{"record":{"id":"22ea8a30a461ee26","repo":"GopeedLab/gopeed","slug":"blob-range-not-allowed","errorCode":null,"errorMessage":"blob range not allowed","messagePattern":"blob range not allowed","errorType":"http","errorClass":null,"httpStatus":416,"severity":"warning","filePath":"internal/blob/registry.go","lineNumber":338,"sourceCode":"\tif err != nil {\n\t\thttp.NotFound(w, req)\n\t\treturn\n\t}\n\tmeta, open, session := src.acquireOpen()\n\tif open == nil {\n\t\thttp.NotFound(w, req)\n\t\treturn\n\t}\n\tif session != nil {\n\t\tdefer session.Release()\n\t}\n\tif src.sourceError() != nil {\n\t\thttp.Error(w, \"blob source unavailable\", http.StatusGone)\n\t\treturn\n\t}\n\tstart, end, ranged, err := parseRange(req.Header.Get(\"Range\"), meta.Size, meta.Range)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)\n\t\treturn\n\t}\n\tif !meta.Range {\n\t\tstart, end, ranged = 0, -1, false\n\t}\n\treader, err := open(req.Context(), OpenRequest{\n\t\tOffset: start,\n\t\tEnd:    end,\n\t})\n\tif err != nil {\n\t\tif req.Context().Err() == nil || !errors.Is(err, req.Context().Err()) {\n\t\t\tsrc.recordSourceError(err, meta.Range)\n\t\t}\n\t\thttp.Error(w, \"blob source unavailable\", http.StatusGone)\n\t\treturn\n\t}\n\tif reader == nil {\n\t\terr = fmt.Errorf(\"%w: opener returned a nil reader\", ErrSourceClosed)","sourceCodeStart":320,"sourceCodeEnd":356,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/internal/blob/registry.go#L320-L356","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check Registry.Metadata(url) first: only send Range when meta.Range is true AND meta.Size > 0; otherwise omit the header","When creating sources, pair Range:true with a positive Size, or use Range:false for empty/unknown-size blobs (CreateOpener enforces this)","Drop the Range header and re-request — non-ranged requests bypass range handling entirely"],"exampleFix":"// before\nurl, _ := reg.CreateOpener(open, &blob.CreateOptions{Range: true}) // Size 0: invalid combo\nreq.Header.Set(\"Range\", \"bytes=0-\") // -> 416 blob range not allowed\n\n// after\nurl, _ := reg.CreateOpener(open, &blob.CreateOptions{Range: false}) // empty blob: no ranges\n// or register the real size:\n// &blob.CreateOptions{Range: true, Size: size}","handlingStrategy":"validation","validationCode":"meta, err := reg.Metadata(blobURL)\nif err != nil {\n\treturn err\n}\nif !meta.Range || meta.Size <= 0 {\n\treq.Header.Del(\"Range\") // never send Range to non-ranged or empty sources\n}","typeGuard":null,"tryCatchPattern":"resp, err := client.Get(blobURL)\nif err == nil && resp.StatusCode == http.StatusRequestedRangeNotSatisfiable { // 416\n\tif strings.Contains(resp.Status, \"range not allowed\") || true {\n\t\t// retry once without the Range header\n\t\treq.Header.Del(\"Range\")\n\t}\n}","preventionTips":["Only attach Range headers when Metadata reports Range:true and Size > 0","Register ranges with a positive size or leave Range:false for empty blobs","Strip default Range headers in shared HTTP clients before blob requests"],"tags":["http-416","range","blob-registry","options"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}