Step 1 of 5 · Reading · ~4 min
Read
Concurrency & Persistence
Keep-Alive Connections
HTTP/1.0 opened a TCP connection, sent one request, read one response and closed. Every image, stylesheet and script on a page paid for its own handshake, and on a 100 ms link that is 100 ms of silence per file. HTTP/1.1 inverted the default: the connection stays open until somebody says otherwise. After this lesson your program reads a whole connection's worth of bytes, request after request, and decides after each one whether the socket survives.
What reuse buys
A new TCP connection costs a round trip before any data moves, and TLS adds one or two more. TCP also starts every connection with a small congestion window and ramps up. A reused connection has already paid for all of it.
Two signals, and who decides
Connection: keep-alive HTTP/1.0: "please stay open". HTTP/1.1: redundant but legal
Connection: close either version: this is the last message on this socket
Connection is a hop-by-hop header: it describes this one link, not the request's journey. Its value is a comma-separated list of tokens, matched case-insensitively, and the header may appear more than once. Connection: Keep-Alive, Upgrade contains the token keep-alive. A parser that compares the raw string to keep-alive misses it.
The decision is a table: the version supplies the default and the tokens override it.
| Request | no relevant token | keep-alive token | close token |
|---|---|---|---|
| HTTP/1.1 | keep | keep | close |
| HTTP/1.0 | close | keep | close |
If both tokens are present, close wins: a single vote to close is enough.
The loop, as steps
- Accept the connection and set the request counter to zero.
- Parse one complete request: request line, headers, and exactly the body the framing headers describe.
- Answer it, then apply the table above.
- If the verdict is close, or the counter has reached the server's cap, stop. Otherwise go back to step 2.
- If the client closes while you wait for the next request, that is a normal end of the connection.
Step 2 is where everything from the body lesson stops being pedantry. A single-shot server hides framing bugs, because the connection ends and nobody notices unread bytes. A keep-alive server shows every one of them: read one byte too few and the tail of a body is parsed as the next request line. Consider this stream, which is two requests:
POST /a HTTP/1.1\r\nHost: x\r\nContent-Length: 26\r\n\r\n
GET /smuggled HTTP/1.1\r\n\r\n <- 26 bytes of body, not a request
GET /b HTTP/1.1\r\nHost: x\r\n\r\n
A server that scans for the next "GET " would see three requests. Byte counting sees two.
A real server also needs an idle timeout (15 to 60 seconds is typical) and a cap on requests per connection, because a client that vanishes without a FIN leaves you holding a file descriptor forever, and enough of those make accept() fail while the process looks healthy. This course uses a cap of 4 requests per connection: the fourth request is answered with close whatever it asked for.
Pipelining, and what replaced it
HTTP/1.1 lets a client send several requests without waiting for the responses. Responses must come back in request order, so one slow handler stalls everything behind it. Browsers turned it off, and HTTP/2 solved the real problem by multiplexing independent streams. Your parser still has to cope with several requests arriving in one read, because that is exactly what the exercise sends.
The contract
For each request print <n> <METHOD> <target> KEEP or ... CLOSE. If no bytes remain, print EOF. After CLOSE stop: whatever bytes follow belong to a connection you have closed. A request that cannot be parsed ends the connection with <n> 400 CLOSE: not three space-separated fields, a head that never ends, a Content-Length that is not digits or disagrees with another, or a body shorter than promised. A well-formed line with a version other than 1.0 or 1.1 is <n> 505 CLOSE.
Common mistakes
- Comparing the whole
Connectionvalue instead of its tokens. - Giving HTTP/1.0 the 1.1 default, so old clients hang waiting for a close that never comes.
- Reading the body by scanning for the next request instead of by
Content-Length. - Continuing to parse after a close verdict.
- Forgetting the request cap.
Your exercise
The starter reads the bytes and loops over the requests. You parse each one, frame its body, and make the keep-or-close decision.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…