4.2 Ranges, ETags, conditional GETs, and HEAD

Stores version every resource, which makes caching and partial reads work cleanly. This section covers the HTTP-level features file (and, for ETags, data) give you for free.

ETags: every resource is versioned

Every store read returns an ETag — a version stamp. For file it reflects the stored version; for data it's a content hash of the record. You don't compute it; you use it.

ETags drive two things: conditional reads (below) and optimistic concurrency on writes (next) — the same If-Match mechanism the services config API uses (Part 10), now on every store.

Conditional GET → 304

Send the ETag you already have back as If-None-Match:

GET /files/docs/big.json
If-None-Match: "v7"
→ 304 Not Modified         (no body, ETag echoed)

If the resource hasn't changed, you get 304 with no body — the bytes don't cross the wire. file and data both honor this. Paired with the revalidate caching mode (9.4), this gives you "always fresh, near-zero bandwidth": the client revalidates every time, but only pays for the body when it actually changed.

Conditional writes → 412 (the conditional-write facet)

Every store also returns an ETag on PUT and honours conditional write headers, so a read-modify-write is race-safe:

PUT /files/docs/big.json
If-Match: "v7"            → 200/201 + new ETag   (writes only if still "v7")
                          → 412 Precondition Failed (someone else changed it)

If-None-Match: * is create-only — it writes only if the resource doesn't yet exist (412 if it does). The check is server-side: race-free where the backing adapter has atomic compare-and-swap, best-effort otherwise — your client does the same thing either way (send the header, treat 412 as "re-read and retry"). The facet is declared on the discovery surface as conditional-write.

Range requests → 206 (file only)

file declares the range facet and supports a single Range: header on GET:

GET /files/video/clip.mp4
Range: bytes=0-1048575
→ 206 Partial Content
   Accept-Ranges: bytes
   Content-Range: bytes 0-1048575/...

This is what lets media players seek and large downloads resume. data does not support ranges (it's record-oriented) — check the range facet (3.4) rather than assuming.

Last-Modified and HEAD

file also returns Last-Modified, and HEAD <child> returns all the metadata headers without a body. Use HEAD to check existence, size, or version cheaply before deciding to fetch.

Putting it together

A well-behaved client:

  1. GET a resource, keep its ETag.
  2. On the next fetch, send If-None-Match — take the 304 fast path when unchanged.
  3. For large media, use Range (where the range facet is present) to fetch or resume in chunks.

These are standard HTTP mechanisms; RS2's contribution is that every store implements them uniformly, so the same client logic works across mounts. Next: turning a file mount into a static-site / SPA host.


Previous: 4.1 The file service · Manual home · Next: 4.3 Static-site & SPA hosting →