Step 1 of 5 · Reading · ~4 min
Read
Connection & Parsing
The Request Line
Every HTTP request starts with one line of text. After this lesson your program reads the raw bytes a client sent and answers the first question a server must settle: is this a line I will look at, and if not, which status code do I owe the client?
The bytes on the wire
A browser, curl or a load balancer opens a TCP connection and sends this, byte for byte:
47 45 54 20 2f 73 65 61 72 63 68 3f 71 3d 72 65 64 69 73 20 48 54 54 50 2f 31 2e 31 0d 0a
G E T SP / s e a r c h ? q = r e d i s SP H T T P / 1 . 1 CR LF
Three fields, one space between each, then the two-byte terminator CR LF (0x0d 0x0a, written \r\n). RFC 9112 section 3 gives the grammar:
request-line = method SP request-target SP HTTP-version CRLF
| Field | Example | What it is |
|---|---|---|
| method | GET | the verb. Case-sensitive: get is not GET |
| request-target | /search?q=redis | an opaque string for now. Routing gives it meaning later |
| version | HTTP/1.1 | the literal HTTP/, a digit, a dot, a digit |
Where does the line end?
TCP delivers a stream of bytes, not messages. The only thing that ends the request line is the two bytes CR LF. Two traps live here.
- Cut the stream at
\nalone and every line keeps a stray\ron its end. The version becomesHTTP/1.1\r, which is not equal toHTTP/1.1, and the bug is invisible in a log. - Read the input in your language's text mode and the runtime may rewrite
\r\nas\nbefore you see it. A protocol parser reads bytes.
RFC 9112 lets a server tolerate a bare LF. This course does not. Two components that disagree about where a line ends is how request smuggling starts, so a line that is not terminated by CRLF is a 400. A trailing fragment with no CRLF at all is also a 400: the client has not finished its sentence.
Reject first, accept second
Compare what a real server does with these lines:
GET /a HTTP/1.1 fine
GET /a HTTP/1.1 two spaces: split on SP gives four fields, one empty
get /a HTTP/1.1 lowercase method
GET /a two fields, the version is missing
GET /a HTTP/1.1 \r\n a trailing space makes a fourth field
GET /a HTTP/1.10 the version is digit, dot, digit. Not digit, dot, digits
All five bad lines are 400 Bad Request. Strictness is security: each of them is a place where two parsers can read the same bytes differently.
The contract for this course
Check in this order and report the first rule that fires.
- The line ends in CRLF and holds no other CR or LF.
- It has exactly three fields separated by single spaces, none empty.
- The method is made only of the letters
AtoZ. The target starts with/, or is exactly*when the method isOPTIONS, and every byte of it is printable ASCII (0x21to0x7e). The version has the shapeHTTP/d.d. Any failure is400. - The method is one of
GET HEAD POST PUT DELETE OPTIONS PATCH, otherwise501 Not Implemented: the line is well formed, you just do not do that. - The version is
HTTP/1.0orHTTP/1.1, otherwise505 HTTP Version Not Supported:HTTP/2.0is well formed and you do not speak it.
Syntax errors beat semantic ones, so get / HTTP/2.0 is a 400, not a 505. Between the two semantic rules the method is checked first, so BREW / HTTP/2.0 is a 501.
Common mistakes
- Printing
HTTP/1.1\rbecause you cut the stream at\n. The version check then fails on every valid line. - Splitting on runs of whitespace. That quietly accepts
GET /a HTTP/1.1andGET /a HTTP/1.1, which the contract rejects. - Answering
505forHTTP/1.10orHTTP/1.x. Those are not versions at all, they are syntax errors, so400. - Checking the version before the method shape, so
get / HTTP/2.0comes back505instead of400.
Your exercise
The starter reads stdin as bytes and cuts it at every CRLF for you. You write the judgement for one line: the three-field split, the shape checks and the order of the verdicts. Keep the most likely mistake in mind: a rule that is right on its own but fires in the wrong order.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…