"Every HTTP message — request or response, 1990 or 2026, plain or encrypted — has the same four-part shape. Memorize the shape once; the rest is just filling it in."
One Shape, Forever
An HTTP message has exactly four parts, in this order:
- Start line — one line that says what kind of message this is.
- Headers — zero or more key: value pairs.
- Blank line — a literal CRLF that says headers are done, body begins next.
- Body — optional payload. Can be empty, JSON, form data, an image, a stream of events, anything.
That's it. Every HTTP/1.1 message has those four parts. HTTP/2 and HTTP/3 frame the same content as binary blocks for speed, but the parsed shape your code sees is identical. Learn the shape; everything else is variation on a theme.
Start Line — The Only Place Request and Response Differ
The first line is the only place a request and a response look structurally different.
Request start line has three tokens: METHOD SP URI SP HTTP-VERSION. Example: POST /api/chat HTTP/1.1. That says "I want to POST to /api/chat using HTTP/1.1."
Response start line has three different tokens: HTTP-VERSION SP STATUS-CODE SP REASON-PHRASE. Example: HTTP/1.1 201 Created. That says "Here's an HTTP/1.1 response, status 201, which means Created." The reason phrase is human-text only — clients should never branch on it; branch on the code.
Headers — Metadata About the Message
Headers describe the message and how to handle it. Keys are case-insensitive on the wire (Content-Type, content-type, CONTENT-TYPE all mean the same thing). Values can be anything ASCII; commas can pack multiple values into one header.
Two categories matter most early on:
- Representation metadata (formerly "entity headers") — what's in the body.
Content-Type,Content-Length,Content-Encoding. These can appear on requests OR responses, anywhere there's a body. - Control headers — how to handle the request itself.
Host,Authorization,Accept,User-Agenton requests.Server,Set-Cookie,Cache-Controlon responses.
The Blank Line — Stupid but Mandatory
One single CRLF (Carriage Return + Line Feed) separates the headers from the body. Miss it and the server thinks your body is another header line. Most modern clients write it for you, but if you're hand-rolling a request over a TCP socket, forgetting the blank line is the #1 newbie bug.
Content-Type response header. The header is the truth about the body's format; trying to response.json() a text/html body is the most common cause of "the API gave me garbage."Body — Optional, Described by Headers
A body is optional. GET requests typically have none. DELETE often has none. But every body that exists is described by at least one of two headers: Content-Length (fixed-size body, in bytes) or Transfer-Encoding: chunked (streaming body, length not known up front). cwkPippa's SSE responses use Transfer-Encoding: chunked — same four-part anatomy, just a body that streams event-by-event over a long-lived connection.
The Whole Thing Visualized
Read these byte-by-byte and you've read every HTTP message you'll ever encounter.