Free learning / 45-minute HTTP cache proof lab
HTTP caching: select the right copy before reusing it
Run actual local HTTP requests to expose a wrong-language fresh hit, validate a stored body with 304 and distinguish no-cache, no-store and Vary star.
No account needed. Use your own copy for writing and keep the original as a reference.
Engineering lab library / Run locally
Choose your next agent lab.
Start with the durable workflow reference, then choose the boundary you want to test. Open its walkthrough for setup, commands and expected observations.
Synthetic records. No provider key or model inference. Runs locally with the standard library.
Start here / Python 3.11+ · Standard library
HTTP variants, revalidation and retention
Expose a wrong-language hit, inspect real 304/200 exchanges and withhold an orphan validation response.
Python 3.11+ · Standard library · Owned loopback HTTP · No outside requests
Original LockedIn Labs workshop · 45 minutes. Python 3.11+ standard library. Read a request, a response status and JSON first. The runner starts its own short-lived HTTP origin on a random 127.0.0.1 port, sends actual GETs and closes it. No packages, account, credentials, outside requests or database are used. All instruction text and source revisions are synthetic.
You will distinguish selecting a representation, retaining its bytes and validating a later use. The Caching 101 workshop covers application keys, current authority, expiry and late publication. This workshop adds HTTP evidence; it does not replace those application checks.
1. Separate the address from the representation
A representation is the particular response content selected for a resource. Our /localized address can return English or French. Accept-Language is the request header selecting exactly en or fr here; language preference lists, quality values and regional negotiation are deliberately outside the fixture. The origin responds with Vary: Accept-Language, identifying that selecting request field. A URL-only key can therefore find the wrong saved copy.
Cache-Control describes response caching conditions. The unqualified no-cache requires validation before later reuse; no-store prohibits cache retention. Vary: * prevents a reusable request match. These are separate rules, not alternative spellings of the same instruction. Our small client elects not to retain star responses. HTTP Caching, RFC 9111 is the primary source for these distinctions.
An ETag is an opaque representation validator. Our origin invents tags such as "en-r1"; a real client copies its service's tag instead of guessing it. If-None-Match carries that validator on a conditional GET. A 304 Not Modified response carries no new representation body; it can validate saved bytes rather than supply a replacement document. HTTP Semantics, RFC 9110 defines this response. It differs from a new 200 response containing JSON.
2. Follow the selected body across the boundary
Predict two things separately: whether a request reaches the origin, and which representation the client returns. A cache result is not necessarily an HTTP response that just crossed the network. In the report, network_status: null means this request used a local fresh entry. It does not mean the origin returned a missing or invented status.
The visual compares six independently reset cases from the runner. It is an authored explanation; running the Python file supplies your own HTTP evidence.
UNDERSTAND THE SYSTEM
A stored response must fit this request
Six authored frames explain actual loopback requests from the downloadable Python lab. The diagram does not execute a cache in your browser. Keep origin responses, stored bodies and the result returned to this caller separate.
Watch one example, step by step.
Read the full worked example
- 011 · A fresh hit can be the wrong variant
The URL-only case fetches English, then returns that saved English body to a French request without another origin call. Its age is acceptable; its request match is not. The origin really supplied Vary: Accept-Language.
BROKEN SELECTION: Request fr · returned en · origin calls 1
- 022 · Keep the selecting fields with the body
The guarded case fetches English and French independently, then reuses English for the third request. Inspect two stored entries with their original language headers. The result is two origin calls, not three and not one.
VARIANT-AWARE SELECTION: Requests en / fr / en · origin calls 2
- 033 · No-cache still permits a stored body
The validate route returns no-cache, max-age=600. Its second request must reach the origin. If-None-Match matches the saved validator, so HTTP 304 transfers zero body bytes and the client returns its stored revision 1. A network call still happened.
ACTUAL CONDITIONAL GET: 200 then 304 · stored body returned
- 044 · A changed representation needs new bytes
After the second validation, the fixture driver advances the origin to revision 2. The third conditional request still presents r1. The origin returns 200 with r2 bytes, and the matching cache entry is replaced. The second request's 304 did not promise future immutability.
ACTUAL SOURCE CHANGE: 200 / 304 / 200 · returned revision 2
- 055 · Do not confuse retention with validation
No-store and Vary-star each make two origin requests in separately reset cases, with zero retained entries under this fixture's conservative policy. No-store forbids cache retention; star prevents a reusable request match. The reasons differ, even when these observed counts agree.
TWO DISTINCT CASES: 200 / 200 each · retained entries 0
- 066 · A 304 cannot invent a missing body
The orphan case deliberately injects a faulty upstream 304 into an empty cache, without a conditional request. The client records the real 304 and zero transferred body bytes, then withholds because no matching representation exists. It does not turn absence into an empty successful document.
FAULTY UPSTREAM RESPONSE: 304 · no entry · result withheld
Read the connections in plain English
- 01Current request → Stored candidate
Use current request fields, not just the resource address.
- 02Representation bytes → Stored candidate
Retain the original selecting header values beside each saved body.
- 03Stored candidate → Owned HTTP origin
A missing variant or no-cache requirement reaches the owned origin.
- 04Owned HTTP origin → Reuse decision
Separate a complete 200 representation from a bodyless 304 response.
- 05Reuse decision → Representation bytes
This fixture stores eligible 200 bodies and refreshes matching validated entries.
- 06Representation bytes → Reuse decision
A validator cannot supply bytes that were never stored.
- 07Reuse decision → Receiving evidence
Record the actual representation or an explicit unresolved result.
Check your understanding: Does no-cache mean nothing can be stored, or that a repeat avoids the network?
Neither. In this authored case, the body is stored, but reuse requires validation. The second call receives an actual 304 with no body, then returns the matching saved bytes. No-store is a different retention rule. Neither header is application authorization or proof of overall privacy.
3. Run six actual HTTP comparisons
Download and read the runner. Keep an untouched reference and write predictions before opening its JSON output:
python3 --version
python3 fde-http-cache.py > cache-original.json
python3 fde-http-cache.py --case url-only-variant --first-language fr > cache-reversed.jsonEach case has its own origin and empty cache. The driver performs requests in a deliberate sequential order; this is not concurrent traffic, a CDN or a browser cache test. Successful completion records listener_closed: true only after the owned server thread stops and its listener socket closes.
| Case | Network status sequence | Actual result to explain |
|---|---|---|
url-only-variant | 200, local reuse, local reuse | en / fr / en requests all receive English; only one origin request |
vary-aware | 200, 200, local reuse | en / fr / en receive their respective language; two stored entries |
no-cache-revalidation | 200, 304, 200 | Stored r1 reused after validation, then replaced with r2 bytes |
no-store | 200, 200 | Both requests reach origin; no reusable entry retained |
vary-star | 200, 200 | No request match; this client retains no entry |
orphan-304 | 304 | Deliberately faulty upstream response; result withheld with no body invented |
Open the written ledger after inspecting the diagram:
url-only-variant, request 2:
requested_language: fr
source: stored-fresh
network_status: null
representation.language: en
origin_exchanges count: 1
vary-aware, request 2:
requested_language: fr
source: origin-body
network_status: 200
representation.language: fr
origin_exchanges count: 2 after all three requests
no-cache-revalidation, request 2:
If-None-Match: "en-r1"
origin response: 304, body_bytes: 0
source: validated-stored
representation: saved English revision 1
no-cache-revalidation, request 3:
If-None-Match: "en-r1"
origin response: 200, ETag: "en-r2", new JSON bytes
source: origin-body
representation: English revision 2Read origin_exchanges beside steps, not just a hit count. The origin actually received a conditional GET in the 304 step. Zero response-body bytes is not zero network traffic, zero header bytes, a measured latency gain or an avoided model call. The fixture doesn't use a model.
In stored_entries, compare each body's language with its saved request_headers. The guarded selection keeps both together. The deliberately broken selection ignores only Vary; it still checks age and no-cache. A young entry can therefore be the wrong representation without being expired.
4. Change the first request without changing its meaning
Compare cache-original.json with cache-reversed.json. The latter starts in French, requests English next, then French again. The URL-only cache now returns French to the English request. Reversing the order changes the wrong answer, not the violated rule. The requested language remains the independent target.
Run the guarded counterpart with the same changed input:
python3 fde-http-cache.py --case vary-aware --first-language fr > cache-guarded-fr.json
python3 fde-http-cache.py --case no-cache-revalidation --first-language fr > cache-validated-fr.jsonBoth selected variants still match the requests. In the validation case, tags are now "fr-r1" and "fr-r2". Never compare a tag's apparent digits as a universal ordering rule. The origin owns the validator and its selected content. The driver advances the source only after the second request; no HTTP request from the learner mutates an outside service.
5. Diagnose before changing cache policy
The validate route returns no-cache, max-age=600. Read the client's no-cache branch before its age comparison. The second request validates despite being young. The 304 has the same authored ETag, Vary and Cache-Control; the client keeps the old body and refreshes its age record. The third request reaches the origin again and receives new bytes after the source revision changes.
The no-store route also advertises max-age, but the client stores no reusable entry. The report still shows the synthetic content delivered to its caller. No-store is not a promise that application reports, screenshots, logs or copies are erased. Do not put private records in this exercise.
The star case also retains zero entries, by this client's conservative choice. Do not conclude that the standard universally forbids storing a Vary-star response: its important reuse boundary is the absence of a matching request.
The orphan case intentionally violates the expected upstream exchange: it returns 304 to an unconditional request when no body has been stored. The client returns representation: null with reason no_matching_stored_representation. A receiver should investigate that exchange rather than treat an empty document as a successful response. The fixture withholds on other unsupported statuses.
Read LocalCache.request. Its supported scope is deliberately narrow: GET, these fixed paths/directives, one exact language header, complete JSON bodies, and unchanged validation metadata. It estimates local age conservatively from Date, observed response delay and monotonic residence; it never sleeps for a TTL experiment. It implements no general cache parser, shared authorization, cookies, TLS, partial responses, redirects, stale-on-error or browser service worker. Real 304 metadata updates and other HTTP cases need a complete cache implementation and receiving-environment tests. Use maintained infrastructure for production; do not deploy Python's teaching server or this small cache.
6. Receive the cache contract as a delivery pod
Use three roles, rotating the receiver on another run:
- Builder: retain command, runtime, request headers, network observations, returned representation and source-change ordering. Show the exact broken rule.
- Receiver: reverse the first language without author takeover. Locate the wrong copy, both guarded entries, and a bodyless 304 with its matching body.
- Application/platform owner: name who owns representation selection, intermediary configuration, current authorization and unsupported responses. Keep an unresolved receiving-environment experiment with a bounded stop rule.
Each person keeps an individual record; a shared correct explanation cannot establish every participant's contribution:
Learner / run / exact command:
Prediction before execution:
Requested language / returned language / source:
Original request headers and origin exchange reference:
Observed status / validator / network body bytes:
Stored entry and original selecting fields:
My contribution / assistance received:
Changed input / prediction before rerun / observed evidence:
Receiving action actually attempted / assistance:
Unresolved behavior / accountable owner / next bounded experiment:For an enterprise document API, identify the actual browser, proxy, gateway and application cache boundaries. Use synthetic content and an approved receiving route. Capture actual response headers and chosen representation through that route, then test changed preferences, source revisions and unavailable origin behavior. Language matching establishes neither tenant isolation nor current access. A provider's private/public setting is not a substitute for the application's resource authorization contract.
7. Defend it in an interview
Explain: Can a fresh hit be wrong? Use request 2 in the broken variant case, including requested and returned language and the absent second origin request.
Debug: Why does no-cache return stored bytes after a 304? Locate the exact conditional request, zero new body bytes and the retained r1 representation. Then explain why the third call receives r2 bytes.
Design: Specify a cache contract for a scoped enterprise source. Name the cached object, selecting fields, validation/retention policy, actual intermediary, authorization owner, unsupported-response behavior and receiving evidence. State what this local experiment leaves untested before recommending a rollout.
Primary references checked 28 October 2026: HTTP Caching, HTTP Semantics, and Python HTTP client. The runner, comparisons, diagram, interview prompts and receiving exercise are original instruction derived from the observed fixture behavior.