Step 1 of 5 · Reading · ~4 min
Read
Caching, Compression & Beyond
Range Requests
A 4 GB video, a 900 MB disk image, a set of model weights: a protocol that can only say "send me the whole thing" makes every one of those a gamble on the connection holding. Range requests are HTTP's answer — the client asks for a byte slice and the server sends exactly that slice. Resumable downloads, video seeking, and reading a ZIP file's index without fetching the archive all fall out of the same small feature.
Asking for a slice
Range: bytes=0-1023 # the first 1024 bytes
Range: bytes=1024- # from byte 1024 to the end
Range: bytes=-500 # the LAST 500 bytes (a suffix range)
Range: bytes=0-499,1000-1499 # multiple ranges (rare; mostly PDF readers)
Two things about this grammar cause almost every bug written against it. Both endpoints are inclusive: bytes=0-1023 is 1024 bytes, not 1023, so the length is always end - start + 1. An off-by-one here does not crash — it truncates one byte off every chunk, and a resumed download produces a corrupt file whose checksum fails long after anyone remembers why.
And a leading - is not a negative number. bytes=-500 is a suffix range meaning the last 500 bytes; the field before the dash is empty, not signed. A parser that reaches for an integer conversion on the whole spec gets -500 and cheerfully computes a start of minus five hundred. Suffix ranges are genuinely useful — a client fetches a file's trailing directory or a log's tail without knowing the total size — so this branch is not an exotic edge case.
Answering with a slice
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-1023/45678 # which bytes, and the total size
Content-Length: 1024 # length of THIS slice, not the file
Accept-Ranges: bytes
...1024 bytes...
206, not 200 — the status is how a client knows it received a fragment rather than a short file. Content-Range reports the slice and the total, which is how a client that guessed a range discovers the real size. Content-Length describes only what is in this response; setting it to the file size here is the framing bug from the body lesson, and it hangs the client waiting for bytes that will never arrive.
Accept-Ranges: bytes on ordinary 200 responses is the advertisement that makes any of this happen — a download manager reads it and decides whether resuming is possible. Sending Accept-Ranges: none, or nothing at all, tells clients not to try.
Clamping, and when to refuse
Ranges arrive from clients working with stale information, so a server has to decide which malformed asks are correctable and which are not:
- End past the last byte —
0-9999on a 1000-byte file — is clamped to0-999. The client asked for more than exists; sending what exists is the useful answer. - A suffix larger than the file —
-2000on 1000 bytes — is likewise the whole file,0-999.max(0, size - n)handles it without a special case. - Start at or past the end —
2000-on 1000 bytes — cannot be clamped into anything meaningful, so it is416 Range Not Satisfiable. - End before start —
500-400— is nonsense and also a416, as is a spec you cannot parse at all.
Every 416 must carry Content-Range: bytes */<size>. The * says "no slice here", and the size is the point of the response: it tells a client working from a stale length exactly what to ask for next, turning a dead end into one extra round trip.
One security note worth carrying: the range is attacker-controlled arithmetic against a buffer. Validate against the actual size before slicing, and treat multi-range requests with suspicion — a request enumerating thousands of tiny ranges makes the server assemble a huge multipart response from a tiny request, which is an amplification primitive that has taken real servers down.
Your exercise: Resolve a Range Request
Size and range spec in, 206 <start>-<end>/<size> <length> or 416 */<size> out. The ladder is the case table above, one rung at a time: an ordinary range, an end past the file that must clamp, a start-only range running to the last byte, a suffix range, a suffix bigger than the file, a single-byte range where the inclusive arithmetic is most visible, a start beyond the end, a reversed pair, and an empty spec. Nine cases, roughly twenty lines — and the inclusive end - start + 1 appears in exactly one of them, which is why it is the one people get wrong.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…