Stage 1: Parse the Request
stage 1 of 4 · ~20 min · runs in your browser
Objective:Understand the HTTP/1.1 wire format at the byte level — the foundation every web framework sits on top of.
A raw HTTP/1.1 request is lines of text separated by \r\n, a blank
line (\r\n\r\n), then an optional body. Before any framework existed,
someone wrote this function — now it's your turn.
Implement parseRequest(raw) returning:
method— e.g."GET"(first token of the request line)path— e.g."/users/42"(without the query string)query— object of decoded query params (?q=db%20engine&page=2→{ q: "db engine", page: "2" });{}when absentheaders— object with lowercased header names (header names are case-insensitive per RFC 9112; values keep their case, surrounding whitespace trimmed)body— everything after the first blank line,""if none
Structure of an HTTP/1.1 request:
METHOD /path?query HTTP/1.1\r\n
Header-Name: value\r\n
Another-Header: value\r\n
\r\n
body (optional)
The blank line is always exactly \r\n\r\n — one CRLF after the last
header. The body can itself contain blank lines; only the first
\r\n\r\n separates headers from body.
Why this matters in production
Nginx parses millions of these per second. Express, Fastify, and every other Node.js framework call something structurally identical to what you're about to write. When a malformed request causes a 400 or a header injection slips through, this is the code that either catches it or doesn't.
tests (6)
○ parses the request line
○ lowercases header names and trims values
○ splits and decodes the query string
○ captures the body
○ body may itself contain blank lines
○ header value with colon