GopeedLab/gopeed · warning

blob range not allowed

Error message

blob range not allowed

What it means

HTTP 416 whose body is ErrRangeNotAllowed ('blob range not allowed'). parseRange returns it when the request carries a Range header and the source has range support enabled, but its registered size is not positive (size <= 0). The public CreateOpener API already rejects Range:true together with Size<=0 ('range requires positive size'), so through normal API use this is a defensive branch — it fires only for range-enabled sources whose recorded size is zero.

Source

Thrown at internal/blob/registry.go:338

	if err != nil {
		http.NotFound(w, req)
		return
	}
	meta, open, session := src.acquireOpen()
	if open == nil {
		http.NotFound(w, req)
		return
	}
	if session != nil {
		defer session.Release()
	}
	if src.sourceError() != nil {
		http.Error(w, "blob source unavailable", http.StatusGone)
		return
	}
	start, end, ranged, err := parseRange(req.Header.Get("Range"), meta.Size, meta.Range)
	if err != nil {
		http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)
		return
	}
	if !meta.Range {
		start, end, ranged = 0, -1, false
	}
	reader, err := open(req.Context(), OpenRequest{
		Offset: start,
		End:    end,
	})
	if err != nil {
		if req.Context().Err() == nil || !errors.Is(err, req.Context().Err()) {
			src.recordSourceError(err, meta.Range)
		}
		http.Error(w, "blob source unavailable", http.StatusGone)
		return
	}
	if reader == nil {
		err = fmt.Errorf("%w: opener returned a nil reader", ErrSourceClosed)

View on GitHub (pinned to 7b7327ffb3)

Solutions

  1. Check Registry.Metadata(url) first: only send Range when meta.Range is true AND meta.Size > 0; otherwise omit the header
  2. When creating sources, pair Range:true with a positive Size, or use Range:false for empty/unknown-size blobs (CreateOpener enforces this)
  3. Drop the Range header and re-request — non-ranged requests bypass range handling entirely

Example fix

// before
url, _ := reg.CreateOpener(open, &blob.CreateOptions{Range: true}) // Size 0: invalid combo
req.Header.Set("Range", "bytes=0-") // -> 416 blob range not allowed

// after
url, _ := reg.CreateOpener(open, &blob.CreateOptions{Range: false}) // empty blob: no ranges
// or register the real size:
// &blob.CreateOptions{Range: true, Size: size}
Defensive patterns

Strategy: validation

Validate before calling

meta, err := reg.Metadata(blobURL)
if err != nil {
	return err
}
if !meta.Range || meta.Size <= 0 {
	req.Header.Del("Range") // never send Range to non-ranged or empty sources
}

Try / catch

resp, err := client.Get(blobURL)
if err == nil && resp.StatusCode == http.StatusRequestedRangeNotSatisfiable { // 416
	if strings.Contains(resp.Status, "range not allowed") || true {
		// retry once without the Range header
		req.Header.Del("Range")
	}
}

Prevention

When it happens

Trigger: A GET with any Range header (e.g. 'Range: bytes=0-') against a source registered with Range:true and a zero/negative size — reachable via hand-constructed Sources, test fixtures, or code paths that bypass CreateOpener's option validation.

Common situations: Test code building Sources directly; empty-blob handling where a client attaches a default Range header to every request; future refactors that change how size is recorded.

Related errors


AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16). Data as JSON: /api/errors/22ea8a30a461ee26. Report an issue: GitHub.