Lab 11 · Range Requests & Byte Serving
Run it:
make lab-11
Source:labs/lab-11-range-requests/main.go
The Problem
A user seeks to 00:45:00 in a 4-hour movie. The full file is 4 GB. Without range requests, the client must:
- Start streaming from the beginning
- Buffer through 2.8 GB before reaching the 45-minute mark
- Or re-download the entire file after a connection drop
This is obviously unacceptable. HTTP Range Requests (RFC 7233) solve this by allowing clients to request specific byte ranges of a resource.
HTTP Range Requests: RFC 7233
The Request
GET /video/movie.mp4 HTTP/1.1
Range: bytes=2097152-4194303
Requesting bytes 2,097,152 to 4,194,303 (a 2 MB chunk starting at the 2 MB mark).
The Response
HTTP/1.1 206 Partial Content
Content-Range: bytes 2097152-4194303/10485760
Content-Length: 2097152
Content-Type: video/mp4
206 Partial Content indicates a successful range request. The full
resource size is 10,485,760 (10 MB) in this example.
Byte Range Syntax
| Format | Meaning |
|---|---|
bytes=0-499 | First 500 bytes |
bytes=500-999 | Second 500 bytes |
bytes=-500 | Last 500 bytes |
bytes=9500- | Bytes from 9500 to end |
bytes=0-0,-1 | First and last byte |
Multipart Range Responses
A single request can ask for multiple disjoint ranges:
Range: bytes=0-50, 100-150
Response:
HTTP/1.1 206 Partial Content
Content-Type: multipart/byteranges; boundary=3d6b6a416f9b5
--3d6b6a416f9b5
Content-Type: text/plain
Content-Range: bytes 0-50/1270
[50 bytes of data]
--3d6b6a416f9b5
Content-Type: text/plain
Content-Range: bytes 100-150/1270
[50 bytes of data]
--3d6b6a416f9b5--
Multipart ranges are rarely used in practice — most video players request sequential single ranges.
Accept-Ranges Header
The server advertises range request support:
Accept-Ranges: bytes
If absent or Accept-Ranges: none, the client knows not to bother with
range requests.
How the CDN Handles Range Requests
Case 1: Full object cached
If the CDN has the full object cached, it can serve any range locally without contacting the origin:
func serveRange(w http.ResponseWriter, r *http.Request, body []byte) {
// Parse Range header
start, end := parseRange(r.Header.Get("Range"), len(body))
w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, len(body)))
w.Header().Set("Content-Length", strconv.Itoa(end-start+1))
w.WriteHeader(http.StatusPartialContent)
w.Write(body[start : end+1])
}
Case 2: Object not cached (miss)
The CDN must fetch the object from origin. Two strategies:
-
Fetch full object: Request the complete file, cache it, serve the requested range. Simpler, but wasteful if the user only watches 5 minutes of a 4-hour movie.
-
Forward range request: Pass the
Rangeheader upstream. The origin returns the exact bytes requested, which the CDN serves and caches. Problem: the CDN now has a partial object cached. Subsequent requests for different ranges must all go to origin.
Most CDNs use a hybrid approach: Range request forwarding with background fetch of the full object. Serve the requested range immediately (low TTFB), fetch the rest in background for future requests.
Case 3: Partial object cached (common in video)
A popular approach is segment-based caching: the CDN maps the byte range to a fixed-size segment (e.g., 1 MB) and caches segments independently. Any range request is decomposed into cached segments plus (at most) two uncached boundary segments.
This is what Akamai Adaptive Media Delivery and AWS CloudFront Media Store do internally for large video files.
Video Player Behavior
HTML5 video players (<video>) issue range requests with a characteristic
pattern:
- Initial fetch:
Range: bytes=0-65535(first 64 KB — moov atom for MP4) - Seek:
Range: bytes=<offset of target timestamp>-<offset+1MB> - Buffer ahead: sequential range requests slightly ahead of playback
- On pause: cancel in-flight range request
- On resume: restart from current position
The CDN sees these as a sequence of range requests to the same URL. Caching the full object ensures all of these can be served locally after the first full miss.
Download Resumption
When a large file download is interrupted (connection drop, browser closed):
Resumed download:
GET /downloads/large-file.zip
Range: bytes=52428800-
↑ Resume from exactly where it stopped (50 MB mark)
Without range support, the user restarts the entire download. With range support, the download resumes from where it was.
The CDN must set ETag or Last-Modified on the initial response so
the client can validate the resource hasn’t changed before resuming:
If-Range: "abc123"
Range: bytes=52428800-
If-Range says: “If the ETag still matches, resume; otherwise send the full
file again.” This prevents serving corrupt data if the file was updated
between the initial download and the resume.
Implementation: http.ServeContent
Go’s standard library provides a complete range request implementation:
func handler(w http.ResponseWriter, r *http.Request) {
body := getContent(r.URL.Path)
reader := bytes.NewReader(body)
modTime := time.Now() // or real modification time
http.ServeContent(w, r, r.URL.Path, modTime, reader)
}
http.ServeContent handles:
Rangeheader parsing and validation206 Partial Contentresponses304 Not ModifiedviaIf-Modified-SinceandIf-None-MatchContent-Rangeheader generation- Multipart ranges
For CDN caching layers, the lab implements manual range handling to show
the full mechanics. For production use of static files, http.ServeContent
or http.ServeFile are correct choices.
What to Measure
# Ratio of 206 vs 200 responses (high ratio = lots of video/download traffic)
rate(http_responses_total{status="206"}[5m]) /
rate(http_responses_total{status="200"}[5m])
# Range request cache hit ratio
rate(cache_hits_total{request_type="range"}[5m]) /
rate(cache_requests_total{request_type="range"}[5m])
# Large object hit ratio (bytes, not requests — often more meaningful)
rate(cache_hit_bytes_total[5m]) /
rate(cache_total_bytes_total[5m])
Try It
make lab-11
# Full file
curl http://localhost:8080/file/video.mp4 -v
# First 10 KB
curl http://localhost:8080/file/video.mp4 -H "Range: bytes=0-10239" -v
# Last 4 KB
curl http://localhost:8080/file/video.mp4 -H "Range: bytes=-4096" -v
# Resume from 50 MB
curl http://localhost:8080/file/large.bin -H "Range: bytes=52428800-" -v
# With If-Range (ETag-validated resume)
ETAG=$(curl -si http://localhost:8080/file/video.mp4 | grep -i etag | awk '{print $2}')
curl http://localhost:8080/file/video.mp4 \
-H "Range: bytes=1024-2047" \
-H "If-Range: $ETAG" -v