Skip to content
Lesson 14 of 16

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-9999 on a 1000-byte file — is clamped to 0-999. The client asked for more than exists; sending what exists is the useful answer.
  • A suffix larger than the file — -2000 on 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 is 416 Range Not Satisfiable.
  • End before start — 500-400 — is nonsense and also a 416, 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.

Up nextVirtual Hosting & the Host HeaderCaching, Compression & Beyond

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…