Course outline · 0% complete

0/25 lessons0%

Course overview →

Anatomy of an HTTP request

lesson 4-1 · ~11 min · 10/25

Lesson 1-2 covered which piece of https://example.com/about names the resource you want.

The path, /about. The scheme picks the protocol, the host picks the server by way of DNS from unit 2, the port picks the door, and the path picks the resource on that server.

This lesson shows exactly how the path gets sent, which is inside an HTTP request.

HTTP is just text

Once the TCP connection from unit 3 is open, the browser speaks HTTP (HyperText Transfer Protocol). The surprise for most beginners is that an HTTP request is plain, readable text. That fact is worth the whole unit, because requests being text means you can read them in debugging tools, write them by hand, and reason about them directly, with no decompiling and no magic. Here is a complete real one:

GET /about HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0

Three parts, always in this order:

  1. Request line: METHOD PATH VERSION. Here that is the method GET, meaning "give me", the path /about, and the protocol version.
  2. Headers: one Name: value per line, carrying extra information about the request. Host says which site you want, since one server can host many sites, and User-Agent says what kind of client is asking.
  3. A blank line, which means "headers are done". Requests that upload data, like form submissions, put a body after the blank line. A GET has no body.

On the wire each line ends with the two characters \r\n (carriage return plus newline), a detail you will see again when reading raw traffic.

GET /about HTTP/1.1Host: example.comUser-Agent: Mozilla/5.0(nothing, a GET has no body)request lineheadersblank line endsthe headersevery line ends with the two characters CR LF
The three parts of an HTTP request: a request line, header lines, then a blank line that ends the headers.

Building a request by hand

This assembles the exact text of an HTTP GET request from three variables.

method="GET"
path="/about"
host="example.com"

echo "$method $path HTTP/1.1"
echo "Host: $host"
echo "User-Agent: learning-shell"
echo ""
echo "(blank line = end of headers, no body for GET)"

Output

GET /about HTTP/1.1
Host: example.com
User-Agent: learning-shell

(blank line = end of headers, no body for GET)

There is no magic here. What a browser sends is a string you could type by hand, which is why HTTP is debuggable in a way binary protocols are not.

The User-Agent is worth noticing: it says learning-shell because that is simply what was written. Nothing verifies it, so any client can claim to be any browser, and servers that make decisions based on it are trusting a self-report.

Parsing a request line back into parts

The same space-trimming pattern from lesson 1-2, run twice.

request="POST /login HTTP/1.1"

method=${request%% *}
rest=${request#* }
path=${rest%% *}
version=${rest#* }

echo "method: $method"
echo "path: $path"
echo "version: $version"

Output

method: POST
path: /login
version: HTTP/1.1

Reading the trims

  • The method peels off first with method=${request%% *}, and the remainder is kept in rest=${request#* }.
  • The same two trims then apply to rest to split the path from the version, which works because a request line has exactly three space-separated fields.
  • That fixed three-field shape is what makes such a small parser correct. A header line would need different handling, since a header value may contain spaces of its own.

What the blank line means

The blank line means the headers are finished, and the body starts next if there is one.

HTTP marks the end of headers with one empty line. For a GET that is the end of the whole request, and for a POST with a body the body follows immediately after it.

Parsers depend on this line absolutely, which is why a malformed request often turns out to be a missing blank line. The server keeps reading, waiting for headers that never end, until it times out.

The design is what lets one connection carry a body of any size. The reader consumes headers until the empty line, learns the body length from a header such as Content-Length, then reads exactly that many bytes.