4.3.24 · D3Computer Networks

Worked examples — HTTP - 1.1 — methods, status codes, headers, persistent connections

3,032 words14 min readBack to topic

This page is the "flight simulator" for HTTP/1.1 — methods, status codes, headers, persistent connections. We will not learn new theory here; we will stress-test what the parent note built by throwing every kind of case at it — every status class, every method flavour, degenerate messages (empty body, no length), limiting behaviour (the latency formula as ), a real-world word problem, and an exam-style trap.

Before we start, one promise: every symbol used below was earned in the parent note or is re-earned here. If a number appears, it is checked at the bottom.


The scenario matrix

Think of this table as a checklist of "boxes the exam can tick". Each worked example below is labelled with the cell it lands in. By the end, every cell has at least one worked example.

# Case class What makes it tricky Example that covers it
C1 2xx success + framing body present → need Content-Length Example 1
C2 3xx conditional / cache 304 has no body — degenerate Example 2
C3 4xx client error server is fine, you erred Example 3
C4 5xx server error blame flips to server Example 4
C5 Idempotency: PUT vs POST repeat-N vs repeat-once Example 5
C6 Degenerate body: chunked / unknown length no Content-Length at all Example 6
C7 Limiting behaviour of latency , RTT large vs small Example 7
C8 Real-world word problem count RTTs on a real page Example 8
C9 Exam twist / trap keep-alive relic, HOL blocking Example 9

Example 1 — 2xx success with a body (cell C1)

Step 1 — pick the status line. HTTP/1.1 200 OK. Why this step? The request succeeded and returns content, so we are in the 2xx class; 200 OK is the default success code when a body is delivered.

Step 2 — count the body bytes. Hello, world! = H e l l o , (space) w o r l d !. That is characters, all single-byte ASCII → 13 bytes. Why this step? On a persistent socket the connection never closes, so the client cannot detect end-of-body by EOF. It must be told exactly how many bytes to read.

Step 3 — assemble.

HTTP/1.1 200 OK\r\n
Content-Type: text/plain; charset=utf-8\r\n
Content-Length: 13\r\n
\r\n
Hello, world!

Why this step? The blank line (\r\n) marks "headers done", and Content-Length: 13 frames the body so the next response on this socket starts cleanly.

Verify: Read exactly 13 bytes after the blank line: Hello, world! — matches. Units: Content-Length is in bytes, and ASCII gives 1 byte/char, so 13 chars = 13 bytes. ✓


Example 2 — 3xx with NO body (degenerate case, cell C2)

Step 1 — recognise the conditional request. If-None-Match: "v7" asks: "only send the body if the ETag is no longer "v7"." Why this step? This is a conditional GET — the whole point is to avoid re-downloading unchanged bytes.

Step 2 — server compares tags. Current ETag is still "v7" = requested tag → resource unchanged. Why this step? The condition "None-Match" fails (it does match), so the server must not send a fresh body.

Step 3 — reply with 304.

HTTP/1.1 304 Not Modified\r\n
ETag: "v7"\r\n
\r\n

Why this step? 304 is a 3xx redirection/conditional response meaning "use your cache." A 304 carries no body by definition, so there is no Content-Length for a body and no body bytes at all — this is the degenerate "zero-body" case.

Verify: Bytes saved = full image size (say B) traded for a header-only reply. Body length = 0. Sanity: had the tag differed, we'd get 200 OK + full body instead — the branch matters. ✓


Example 3 — 4xx client error (cell C3)

Step 1 — locate the resource. Not found on disk, no redirect configured. Why this step? We must distinguish "I broke" from "you asked wrong."

Step 2 — assign blame. The server itself works; the request target is invalid. Why this step? Recall the mnemonic: 4xx = "you messed up", 5xx = "I messed up". A missing resource is the client naming a bad path → client-side.

Step 3 — count the error-body bytes, then reply. The body is <html><body>404 - page not found</body></html>. Counting every character — the two <html>/</html>+<body>/</body> tags, the digits 404, the two spaces, and the hyphen — gives exactly 46 bytes, all single-byte ASCII.

HTTP/1.1 404 Not Found\r\n
Content-Type: text/html; charset=utf-8\r\n
Content-Length: 46\r\n
\r\n
<html><body>404 - page not found</body></html>

Why this step? 404 is the canonical 4xx "resource absent" code. Note it still carries a body (a friendly error page), so it still needs an accurate Content-Length — miscount it and the next response on the socket gets misframed.

Verify: Character count of <html><body>404 - page not found</body></html> = 46 (checked at the bottom of the page). So Content-Length: 46 is correct. ✓


Example 4 — 5xx server error (cell C4)

Step 1 — validate the request. Method, headers, body all well-formed. Why this step? If the request itself were malformed we'd owe a 400 (4xx); it is not.

Step 2 — locate the failure. The server's database crashed, not the client's message. Why this step? Blame flips: the client did nothing wrong, so we're in 5xx.

Step 3 — reply 500.

HTTP/1.1 500 Internal Server Error\r\n
Content-Length: 0\r\n
\r\n

Why this step? 500 is the generic "I messed up." If the DB were merely overloaded/restarting we'd prefer 503 Service Unavailable — but an unhandled crash is 500.

Verify: Contrast with Example 3: same shape of message, opposite blame class. Content-Length: 0 legally means "no body" — the degenerate empty-body case, valid on a persistent socket because the length is still declared (as 0). ✓


Example 5 — idempotency: PUT vs POST repeated (cell C5)

Step 1 — recall the definitions. Idempotent = doing it times leaves the same final state as doing it once. Safe = no state change at all. Why this step? The whole answer hinges on the semantic contract, not the message bytes.

Step 2 — apply to PUT (Case A). PUT "set quota = 5" executed twice → quota is , then set to again = still . Why this step? PUT replaces; replacing with the same value is a no-op the second time → idempotent, safe to retry.

Step 3 — apply to POST (Case B). POST "create order pen" executed twice → two order rows, e.g. IDs 101 and 102. Why this step? POST appends/creates; each call is a new side effect → not idempotent. This is WHY browsers pop "Confirm form resubmission?" for POST but silently retry GET/PUT.

Verify: Model state as counts. Start orders . After one POST: . After a duplicate POST: → not idempotent. Quota starts arbitrary, after one PUT , after duplicate PUT → idempotent. ✓


Example 6 — unknown-length body: chunked encoding (degenerate, cell C6)

Step 1 — see why Content-Length is impossible. The total byte count doesn't exist yet at header-writing time. Why this step? The persistent-connection framing rule demands either Content-Length or another framing mechanism — we must reach for the second option.

Step 2 — switch to chunked transfer encoding.

HTTP/1.1 200 OK\r\n
Transfer-Encoding: chunked\r\n
\r\n
5\r\n
Hello\r\n
6\r\n
 world\r\n
0\r\n
\r\n

Why this step? Each chunk is prefixed by its size in hexadecimal, then the bytes. A final chunk of size 0 marks the end — this replaces "read until EOF."

Step 3 — decode. Chunk 5Hello; chunk 6 world (leading space counts); chunk 0 → done. Why this step? The client concatenates payloads → Hello world, and the 0-chunk tells it where the next response on the socket begins.

Verify: 5 hex = 5 bytes = Hello ✓. 6 hex = 6 bytes = " world" (space+world) ✓. Total decoded body = 11 bytes = Hello world. The 0-sized terminating chunk means we never relied on closing the socket. ✓


Example 7 — limiting behaviour of the latency model (cell C7)

Step 1 — recall the two costs (in RTTs). From the parent, with = HTTP/1.0 load time and = HTTP/1.1 load time (both in RTTs): and . Why this step? These are the earned formulas; we only plug and take limits — no new physics.

Step 2 — read the picture. The figure below is a line plot with the horizontal axis (number of resources, 1 to 40) and the vertical axis time in RTTs. The red line rising steeply is ; the green line rising half as steeply is . At a yellow dashed vertical guide meets both lines at two dots — a red dot at height and a green dot at height — and a yellow double-headed arrow spans the gap between them, labelled "saving = 29 RTT". The key visual: the red line always climbs twice as fast as the green line, so the yellow gap keeps widening as you move right.

Figure — HTTP - 1.1 — methods, status codes, headers, persistent connections

Step 3 — plug . RTT, RTT, saving RTT. Why this step? Confirms the parent's "29 RTTs saved" claim and grounds the limit below (and matches the yellow arrow in the figure).

Step 4 — take the ratio, then . Why this step? The term vanishes, so for a large page HTTP/1.1 asymptotically costs half the RTTs of 1.0 — the handshake saving dominates. In the figure, the green line hugs half the height of the red line far to the right.

Step 5 — scale with RTT. Saving . This is linear in RTT: on a satellite link (RTT ms) the 29-RTT saving ms s; on LAN (RTT ms) it's ms. Why this step? Confirms the parent's forecast — slow networks benefit most.

Verify: At : ratio , just above the limit ✓. Saving in seconds: s ✓ and s ✓.


Example 8 — real-world word problem (cell C8)

Step 1 — count resources. . Why this step? The model is per-resource, so we need first.

Step 2 — HTTP/1.0 time. ms. Why this step? Each of 9 resources pays 1 RTT handshake + 1 RTT request/response.

Step 3 — HTTP/1.1 time. ms. Why this step? One shared handshake (1 RTT) plus 9 serial request/response RTTs.

Step 4 — speedup. faster; absolute saving ms. Why this step? Ties the abstract formula to a number a user feels.

Verify: ✓; ✓; ✓; ✓ (matches the formula). Note: real browsers open ~6 parallel connections, and TCP slow start plus TLS handshake add more RTTs — this toy model is a lower bound. ✓


Example 9 — exam twist / trap (cell C9)

Step 1 — Twist A: recall the default. In HTTP/1.1 persistence is the default; you send Connection: close to opt out. Why this step? The keep-alive header is a HTTP/1.0 relic; requiring it in 1.1 is the classic trap. → Statement is FALSE.

Step 2 — Twist B: apply head-of-line blocking. Pipelined responses must return in request order, so and cannot overtake even though they are ready sooner. Why this step? This ordering constraint is head-of-line (HOL) blocking — the defining flaw of HTTP/1.1 pipelining.

Step 3 — Twist B: compute each arrival time.

  • finishes at ms.
  • is ready after ms of work, but must wait behind ; it is sent right after , arriving at ms.
  • likewise queues behind , arriving at ms.

So all three responses land by 920 ms, and the two "fast" requests were stalled almost a full second by the one slow request in front. Why this step? It quantifies the cost of HOL blocking — this exact stall is what HTTP-2 multiplexing removes by letting independent responses interleave on one connection.

Verify: Serial-in-order total ms ✓. Had been independent streams (HTTP/2), each could finish near ms while ran — so HOL blocking costs about ms of avoidable wait. ✓


Recall Self-test: name the cell

For each, say which matrix cell it hits. A 304 Not Modified with no body ::: C2 (3xx conditional, degenerate zero-body) Duplicate POST creating two rows ::: C5 (idempotency: not idempotent) Streaming a log with Transfer-Encoding: chunked ::: C6 (unknown-length body) 500 from a crashed database ::: C4 (5xx server error) Asymptotic ::: C7 (limiting behaviour)