Follow-up from the ETag support assessment (label: etag-range-followup).
The strong content-hash ETag is already a suitable, tier-stable If-Range validator (a range continuation served from a different tier/pod validates correctly; a changed object correctly forces a full 200). The validator value is fine; the surrounding machinery is entirely missing.
Current gaps
apiv1.getObject (internal/strategy/apiv1.go) does io.Copy(w, cr) — always 200, never honours Range, never sets Accept-Ranges, never returns 206/Content-Range/416.
- The
cache.Cache interface (internal/cache/api.go) only exposes a whole-object Open returning an io.ReadCloser; there is no partial-read / seek capability. Only the disk backend reader happens to be seekable.
checkConditionals does not evaluate If-Range.
Work
- Add a partial-read capability to the cache interface — either a range-aware open (offset + length) or a documented seekable-reader contract — and implement it for
disk, memory, s3 (S3 already does parallel range GETs internally, see s3_parallel_get.go), tiered, and remote.
- Implement server-side range handling in
apiv1.getObject: parse Range, emit Accept-Ranges: bytes, return 206 with Content-Range for satisfiable ranges and 416 for unsatisfiable ones.
- Evaluate
If-Range (against the strong ETag; fall back to full 200 when the validator does not match) before serving a partial response.
- Plumb
Range/If-Range through the client (client/) and remote backend as needed so a tiered remote tier can serve ranges.
Acceptance
Ranged GET against each backend returns the correct bytes with 206/Content-Range; If-Range with a stale ETag returns the full object with 200; unsatisfiable ranges return 416.
Follow-up from the ETag support assessment (label: etag-range-followup).
The strong content-hash ETag is already a suitable, tier-stable
If-Rangevalidator (a range continuation served from a different tier/pod validates correctly; a changed object correctly forces a full200). The validator value is fine; the surrounding machinery is entirely missing.Current gaps
apiv1.getObject(internal/strategy/apiv1.go) doesio.Copy(w, cr)— always200, never honoursRange, never setsAccept-Ranges, never returns206/Content-Range/416.cache.Cacheinterface (internal/cache/api.go) only exposes a whole-objectOpenreturning anio.ReadCloser; there is no partial-read / seek capability. Only the disk backend reader happens to be seekable.checkConditionalsdoes not evaluateIf-Range.Work
disk,memory,s3(S3 already does parallel range GETs internally, sees3_parallel_get.go),tiered, andremote.apiv1.getObject: parseRange, emitAccept-Ranges: bytes, return206withContent-Rangefor satisfiable ranges and416for unsatisfiable ones.If-Range(against the strong ETag; fall back to full200when the validator does not match) before serving a partial response.Range/If-Rangethrough the client (client/) and remote backend as needed so a tiered remote tier can serve ranges.Acceptance
Ranged GET against each backend returns the correct bytes with
206/Content-Range;If-Rangewith a stale ETag returns the full object with200; unsatisfiable ranges return416.