Conditional requests

Turn a repeat download into a few hundred bytes — and use the same mechanism to stop two writers overwriting each other.

07of 18
≈ 12Min read
5Parts

Asking whether anything changed

When a stored response is stale, the browser does not download it again — it asks whether it is still current, carrying what it already has. If the answer is 304, the cached body is reused and the whole exchange is one small round trip.

This is what makes Cache-Control: no-cache cheap: always ask, usually get a 304.

the second request, and the answer nobody pays for
GET /index.html
If-None-Match: "v3"

HTTP/1.1 304 Not Modified
ETag: "v3"
Cache-Control: no-cache
# …and no body at all

Two validators

An ETag is usually a hash of the body or a version number. It compares exactly, it survives a file being rebuilt with the same content, and it works for generated pages where "modified" has no meaning. Send both if you can; the browser prefers the ETag.

Response headerRequest headerNotes
ETag: "v3"If-None-Matchan opaque id for this exact content — the strong one
Last-Modified: …If-Modified-Sinceone-second resolution, and clocks disagree
Weak ETags

W/"v3" says "semantically the same, maybe not byte-identical" — right for a response that is gzipped differently each time. A strong ETag is required for range requests, which is why a video server needs one.

The same trick, in reverse: lost updates

Two people open the same record, both save, and the second write silently erases the first. If-Match turns that into an error the client can handle — optimistic concurrency, for the cost of one header. It is the HTTP version of a database transaction's version check.

If-Match, and the 412 that saves your data
GET /api/orders/482        → 200, ETag: "7"

PUT /api/orders/482
If-Match: "7"              # only if it is still the version I read

→ 200 OK                   # nobody else changed it
→ 412 Precondition Failed  # somebody did: re-read, merge, try again

What a 304 must contain

A 304 has no body and repeats only the headers that could have changed — ETag, Cache-Control, Expires, Vary. The browser merges them into what it already stored. A server that returns a 304 with a fresh Content-Type and no ETag will have the entry revalidated again next time, because there is nothing left to compare.

Where it goes wrong

  • A new ETag on every response — a timestamp, or a hash including the current time. Every revalidation becomes a full download.
  • Different ETags per server in a cluster, because they hash the file path plus the inode. The same file then looks changed on every other request.
  • Returning 200 instead of 304 when the validator matches: correct, and throws away the entire benefit.
  • No validator at all on a no-cache response: every visit re-downloads the whole body.

Practice