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:
- Request line:
METHOD PATH VERSION. Here that is the methodGET, meaning "give me", the path/about, and the protocol version. - Headers: one
Name: valueper line, carrying extra information about the request.Hostsays which site you want, since one server can host many sites, andUser-Agentsays what kind of client is asking. - A blank line, which means "headers are done". Requests that upload data, like form submissions, put a body after the blank line. A
GEThas 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.
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.1Reading the trims
- The method peels off first with
method=${request%% *}, and the remainder is kept inrest=${request#* }. - The same two trims then apply to
restto 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.