Validator Merging¶
When Relaycache has a cached entry and the client sends its own conditional
headers, both sets of validators must be forwarded to the origin so that either
can trigger a 304. The origin’s 304 response then tells us whose cache was
current, allowing Relaycache to return the right status to the client.
ETag (If-None-Match)¶
If-None-Match accepts a comma-separated list of ETags (RFC 7232 §3.2):
If-None-Match: "client-etag", "proxy-etag"
The origin responds 304 if either matches, and echoes the winning ETag
in its 304 response headers.
Merge rules¶
Client has |
Proxy has cached ETag |
Action |
|---|---|---|
No |
No |
nothing |
Yes |
No |
forward client’s header unchanged |
No |
Yes |
inject proxy’s ETag |
Yes, same value |
Yes, same |
forward unchanged (already identical) |
Yes, |
Yes, |
merge: |
Determining who matched¶
After a 304, Relaycache checks the ETag returned by the origin:
Returned ETag in client's If-None-Match list?
Yes → client's cache is current → return 304 to client
No → proxy's ETag matched → return 200 with cached body
If the origin returns no ETag in the 304, fall through to the date check.
Last-Modified (If-Modified-Since)¶
If-Modified-Since can only carry one date. Relaycache sends
max(client_date, proxy_date) — the newer of the two.
Why max?¶
Let:
C = client’s
If-Modified-SincedateP = proxy’s cached
Last-ModifieddateU = actual last-modified date on the origin (unknown to us)
The origin returns 304 when U ≤ forwarded_date.
Case C > P (client is newer than proxy cache)¶
The client has a version we don’t have cached.
Send |
Origin says |
Meaning |
Correct? |
|---|---|---|---|
C (max) |
304 if U ≤ C |
Client is current |
✅ |
C (max) |
200 if U > C |
Both stale; update cache |
✅ |
P (min) |
304 if U ≤ P |
Both current (P < C so U ≤ P ≤ C) |
✅ but rare |
P (min) |
200 if P < U ≤ C |
Wasteful: we’d download a body the client already has |
❌ wasted bandwidth |
Verdict: send C.
Case C < P (proxy is newer — the common case)¶
The proxy has a version the client doesn’t have yet.
Send |
Origin says |
Meaning |
Correct? |
|---|---|---|---|
P (max) |
304 if U ≤ P |
Proxy is current; serve cached body as 200 to client |
✅ |
P (max) |
200 if U > P |
Both stale; update cache |
✅ |
C (min) |
304 if U ≤ C |
Client is current — but C < P so this is a contradiction (the proxy can’t have cached a body newer than the origin’s current version) |
impossible |
C (min) |
200 if C < U ≤ P |
Wasteful: we’d download a body we already have in cache |
❌ wasted bandwidth |
Verdict: send P.
Summary: always send max(C, P)¶
Sending the newer date is strictly better in the asymmetric cases and neutral
in the symmetric case (C == P). max(C, P) minimises unnecessary 200
responses.
Determining who matched (date fallback)¶
When no ETag is present in the 304, compare the origin’s Last-Modified
(from the 304 headers) against the client’s If-Modified-Since:
origin Last-Modified ≤ client If-Modified-Since?
Yes → client's cache is current → return 304
No → proxy's date matched → return 200 with cached body
When no usable validators exist at all, Relaycache conservatively assumes the
proxy matched and returns 200 with the cached body. This is always correct
(the client gets valid content) at the cost of one unnecessary body transfer.