Reading — step 1 of 5
Read
Reading the Request Body
Headers end at the blank line — and then how many bytes of body follow? TCP won't tell you: it's a stream, not a message service, with no "end of request" marker. HTTP solves message framing with two mechanisms, and the second one — chunked encoding, your exercise — is a tiny protocol of its own.
Mechanism 1: Content-Length
The simple case: the sender counts the body's bytes and declares them.
POST /api/notes HTTP/1.1\r\n
Content-Length: 27\r\n
\r\n
{"title":"hello","id":123}
The parser reads the headers, sees 27, then reads exactly 27 bytes — no more (the next bytes may be the next request on a keep-alive connection! over-reading corrupts the stream), no fewer (under-reading leaves body bytes to be mis-parsed as the next request line). Both failure modes are real and nasty; exactness is the contract. Validation applies as always: non-numeric or negative Content-Length is a 400; two different Content-Lengths (last lesson) likewise.
Mechanism 2: chunked — framing without foreknowledge
Now the interesting case: the sender doesn't know the total size — it's streaming (generating output as it goes, proxying, compressing on the fly). Enter Transfer-Encoding: chunked, which frames the body as a sequence of self-describing pieces:
7\r\n
Mozilla\r\n
9\r\n
developer\r\n
0\r\n
\r\n
Grammar per chunk: size in HEX, CRLF, exactly that many bytes of data, CRLF. Repeat. A chunk of size 0 marks the end (followed by a final CRLF — after optional "trailers," which your spec will likely have you skip). Decoded, the body above is Mozilladeveloper — 16 bytes the sender never had to pre-count. The parser is a clean loop:
loop:
read line → parse as hex size (bad hex? 400)
if size == 0: consume trailing CRLF, done
read exactly size bytes (the DATA may contain \r\n freely —
expect CRLF you read by COUNT, not by lines!)
That parenthetical is the classic bug: chunk data is length-delimited, not line-delimited — a chunk can contain CRLFs, and readers that switch to line-reading inside data corrupt everything. Sizes are lines; data is counted bytes; the trailing CRLF after data is a frame separator, not part of the data.
Second classic: the size is hexadecimal. 1A means 26. Parsers that read it as decimal work on small test bodies (where sizes stay under 10) and explode on real ones — a beautifully evil bug, and yes, the graders include a hex-letter size.
Rule of precedence you should also encode: if Transfer-Encoding: chunked is present, it wins over any Content-Length (the spec says ignore CL; security folk say a request carrying both deserves suspicion — it's the request-smuggling primitive — and modern servers often just 400 it).
Your exercise: Decode Chunked Bodies
Chunked streams in; decoded bodies (or 400s) out. The ladder: single chunk → multiple chunks → hex sizes with letters → data containing CRLF → empty body (just the 0 chunk) → malformed size → missing terminator. It's ~20 lines that force the deepest habit of protocol work: read by the grammar, not by what looks like lines — the same discipline as the git packfile parser and every binary format you'll ever meet, here in text where you can still debug by eyeball.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…