gofr-dev/gofr · error

s3 backend did not honor the requested byte range

Error message

s3 backend did not honor the requested byte range

What it means

ErrRangeNotHonored is returned by Read/ReadAt when a byte-range GetObject request was sent (offset > 0) but the backend replied with the whole object and no Content-Range header. Accepting such a response would fill the caller's buffer starting at byte 0, silently serving wrong bytes, so the library refuses the read. It almost always indicates an S3-compatible backend that ignores Range headers.

Source

Thrown at pkg/gofr/datasource/file/s3/file.go:45

	logger       Logger
	metrics      Metrics
	size         int64
	contentType  string
	body         io.ReadCloser
	lastModified time.Time
}

func (*S3File) Sys() any {
	return nil
}

var (
	ErrNilResponse = errors.New("response retrieved is nil ")
	// ErrRangeNotHonored is returned when a byte-range read was requested but the
	// backend answered with the whole object (no Content-Range header). Filling the
	// caller's buffer from byte 0 in that case would silently serve the wrong bytes,
	// so the read is refused instead.
	ErrRangeNotHonored = errors.New("s3 backend did not honor the requested byte range")
)

// Close closes the response body returned in Open/Create methods if the response body is not nil.
func (f *S3File) Close() error {
	bucketName := getBucketName(f.name)

	defer f.sendOperationStats(&FileLog{
		Operation: "CLOSE",
		Location:  getLocation(bucketName)}, time.Now())

	if f.body != nil {
		return f.body.Close()
	}

	return nil
}

// closeBody closes a previously opened response body (if any) before it is

View on GitHub (pinned to 187eb24962)

Solutions

  1. Verify the backend honors Range requests: aws s3api get-object --range bytes=10- ... and check the Content-Range response header.
  2. Replace or reconfigure the non-conformant S3-compatible storage/proxy (MinIO/Ceph/nginx caching layer) so Range is honored.
  3. In tests, make the fake GetObject implementation return ContentRange (e.g. aws.String("bytes 10-99/100")) whenever a Range input is present.
  4. As a workaround, Seek back to 0 and read the whole object sequentially, since offset 0 sends no Range header.

Example fix

// test mock that triggers the error
res := &s3.GetObjectOutput{Body: body} // no ContentRange
// after
res := &s3.GetObjectOutput{Body: body, ContentRange: aws.String(fmt.Sprintf("bytes %d-%d/%d", off, total-1, total))}
Defensive patterns

Strategy: fallback

Validate before calling

// probe backend range support once at startup:
out, err := client.GetObject(ctx, &s3.GetObjectInput{Bucket: b, Key: k, Range: aws.String("bytes=0-0")})
rangeOK := err == nil && out.ContentRange != nil

Try / catch

n, err := f.Read(buf)
if errors.Is(err, s3datasource.ErrRangeNotHonored) {
    // fallback: seek to 0 and stream the whole object sequentially
    f.Seek(0, io.SeekStart)
    return f.Read(buf)
}

Prevention

When it happens

Trigger: 1) (*S3File).Read with f.offset > 0 while res.ContentRange == nil; 2) (*S3File).ReadAt (which always sends a Range header) against a backend that ignores it. Both wrap nothing — the sentinel is returned directly.

Common situations: Using MinIO, Ceph, mock S3 servers, or caching proxies that ignore the Range header; serving reads after Seek pushed the offset past 0; test double S3 clients that return a full body without ContentRange.

Related errors


AI-assisted analysis of gofr-dev/gofr@187eb24962 (2026-09-01). Data as JSON: /api/errors/b964aff4ca16fb8e. Report an issue: GitHub.