LearnThe Network StackThe Application Layer

HTTP

curl -v - a hands-on Linux lab on a real virtual machine.

Request line, headers, status code, body. The protocol you use a thousand times a day, seen raw.

You have sent a million of these and read none of them

Every web page you have ever loaded started with an HTTP request. Your browser sent a message that said GET / HTTP/1.1 and the server sent back a status code, some headers, and the page content. This happens thousands of times a day, completely hidden behind the browser interface.

It is also plain text. Not a binary format, not a protocol you need a special tool to decode. Lines of readable text, sent over a TCP connection, that you could type by hand if you had to.

This lesson strips away the browser. You will send HTTP requests yourself and read every line of the conversation, then capture the same exchange on the wire and watch it ride the stack you have spent this whole track building.

The black boxes below are a practice terminal: a safe sandbox that checks the one command each step teaches. The lab user is student and the machine is named http-lab. Values that change on every run, such as tool versions, timestamps and port numbers, are printed as labels rather than numbers, because your run will produce its own.

What HTTP actually is

HTTP stands for HyperText Transfer Protocol. It is the protocol that powers the web. When your browser loads a page, it sends an HTTP request to a server, and the server sends back an HTTP response.

An HTTP request has three parts:

1. Request line - The method (GET, POST, PUT, DELETE), the path (/index.html), and the HTTP version. 2. Headers - Key-value pairs that carry metadata. Host, User-Agent, Accept, Content-Type. 3. Body - Optional data sent with the request (used in POST and PUT).

An HTTP response also has three parts:

1. Status line - The HTTP version, the status code (200, 404, 500), and a reason phrase. 2. Headers - Metadata about the response. Content-Type, Content-Length, Server. 3. Body - The actual content. HTML, JSON, an image, a file.

Three parts each way, and the shape never changes. Once you can see that shape on a screen, every web API, every browser developer tools panel and every load balancer log reads the same way.

The protocol that started as one line

The first version of HTTP, written by Tim Berners-Lee around 1991 and known afterwards as HTTP/0.9, was one line long. The client sent GET /page and the server sent back the document. No headers. No status codes. No version number, because there was only one version. When the document ended, the server closed the connection, and that was how you knew it had finished.

Status codes and headers arrived with HTTP/1.0 in 1996, and persistent connections with HTTP/1.1 the year after. Everything you are about to read on screen, the Host: line, the 200, the Content-Length, is a patch on that original one-line idea.

The lab server you are about to talk to speaks HTTP/1.0, not 1.1. That is not a mistake in the lab. It is a small, very common Python web server. Watching a modern client and an old server agree on a version is one of the more useful things on the screen you are about to read.

Your lab network

This lab builds two machines inside your one VM using network namespaces. A network namespace is a private copy of the network stack: its own interfaces, its own addresses, its own routing table. They are joined by a virtual cable:

  [client]  10.0.1.10  <-------->  10.0.1.20  [server]

The 10.0.1.0/24 network exists only inside those namespaces, so a command typed plainly in your shell cannot reach the server. To run something inside the client namespace, put sudo ip netns exec client in front of it. The send-curl and cap helpers already do that.

The server runs an HTTP service on port 80. It serves two files:

These command lines get long, so lean on tab completion. Type send- and press Tab and the shell finishes send-curl. Tab completes command names and file paths alike, and it is the cheapest way to stop a typo halfway through a long line.

Your progress is tracked automatically. Just type the commands naturally and you will see a checkmark appear when each task completes.

Commit: which version comes back?

curl is a command-line tool that sends HTTP requests. The -v flag (verbose) shows you everything: what curl sent and what the server sent back. curl will announce itself as HTTP/1.1, because that is what curl speaks.

Send the request by hand

The helper runs sudo ip netns exec client curl -v http://10.0.1.20/ for you.

The output is laid out as shown below. The exact curl and Python version strings depend on what is installed, and the Date header is the clock on the server at the moment you asked.

send-curl

prompt: student@http-lab:~$ answer: send-curl ||| sudo ip netns exec client curl -v http://10.0.1.20/ output:

There is the whole conversation, and the version question is settled on the first < line: HTTP/1.0 200 OK.

Look at the left edge before you read anything else. Every line starts with one of three characters, and those characters are curl telling you who said what. That is the single most useful habit in this entire lesson, because it is the difference between reading a protocol and reading a wall of text.

Making this request and reading what came back is the first of the five things your lab grades.

Commit: 21 characters, 22 bytes

The body of that response reads Hello from the server. Count the characters and you get 21. The server said Content-Length: 22.

Read the conversation line by line

Now walk the whole screen in order:

The bare > and the bare < are worth a second look. Each one is a blank line, and a blank line is how HTTP says the headers are finished and the body starts. It is not decoration and it is not optional.

Watch the same request from the wire

Now capture that request from the network side. Order matters: the whole exchange is over in milliseconds, so the capture has to be listening before the request goes out. This waits three seconds in the background while tshark gets going, then sends the request:

(sleep 3; send-curl) &

Now start the capture in that window. -a duration:8 stops the capture after eight seconds and hands your prompt back. -Y http is a display filter: capture everything, show only HTTP.

Before you run it, commit to a number: how many packets did that exchange take, and how many of them will this filter show you? Packet numbers and timestamps will be whatever your run produces.

cap -a duration:8 -Y http

prompt: student@http-lab:~$ answer: cap -a duration:8 -Y http ||| cap -Y http -a duration:8 output: 1 0.000123 10.0.1.10 -> 10.0.1.20 HTTP GET / HTTP/1.1 2 0.001234 10.0.1.20 -> 10.0.1.10 HTTP HTTP/1.0 200 OK (text/html) hint: The cap helper runs tshark inside the client namespace: cap -a duration:8 -Y http

tshark shows you the HTTP layer decoded. The request method (GET), the path (/), the response status (200 OK), and even the content type. This is HTTP riding on TCP, riding on IP, riding on Ethernet. Every layer you have seen in this track, working together.

Compare the two screens. curl showed you eleven lines of conversation. tshark shows the same conversation as two packets, because everything curl printed as separate lines travelled together inside one packet each way. Capturing it is the second of the five things your lab grades.

Only two lines, for a request that actually took around ten packets. The display filter hid the TCP handshake, the acknowledgements and the teardown, because none of those carry HTTP. Drop -Y http and you see all of it.

Commit: two lines, one of them indented

Summary lines are for reading. When you want values to feed to another program, -T fields prints field values instead, and -e names each field you want. Ask for two fields, the request method and the response code, and two lines come back: one for the request packet and one for the response packet.

Pull out just the fields you want

Start the traffic first, exactly as before:

(sleep 3; send-curl) &

Then ask for the two fields by name. This is the form you reach for in scripts, because the output is columns rather than prose.

cap -a duration:8 -Y http -T fields -e http.request.method -e http.response.code

prompt: student@http-lab:~$ answer: cap -a duration:8 -Y http -T fields -e http.request.method -e http.response.code output: GET 200 hint: The same capture with field output switched on and two fields named: cap -a duration:8 -Y http -T fields -e http.request.method -e http.response.code

Two lines, two columns, no prose. GET with an empty second column, then an empty first column with 200.

Here are some useful HTTP fields:

FieldWhat it shows
http.request.methodGET, POST, PUT, DELETE
http.request.uriThe path requested (/index.html, /data.json)
http.response.codeStatus code (200, 404, 500)
http.hostThe Host header value
http.content_typeThe Content-Type of the response
http.content_lengthThe size of the response body in bytes
http.user_agentThe User-Agent header (identifies the client)

Every one of those field names is the same name tshark shows in a verbose dissection, which is the next step. When you do not know what a field is called, dissect one packet with -V and read the name off the screen. Extracting fields this way is the third of the five things your lab grades.

Open one packet all the way up

-V prints every layer of a packet instead of a summary line, and the http.request filter narrows the capture to the request itself, so you get one packet rather than a screenful.

Start the traffic first again:

(sleep 3; send-curl) &

You will see the full stack, top to bottom. The frame size and the source port on the client are different on every run.

cap -a duration:8 -V -Y "http.request"

prompt: student@http-lab:~$ answer: cap -a duration:8 -V -Y "http.request" ||| cap -a duration:8 -V -Y http.request ||| cap -a duration:8 -Y "http.request" -V output: Frame <n>: <size> bytes on wire

Ethernet II, Src: 02:00:0a:00:01:0a, Dst: 02:00:0a:00:01:14 Type: IPv4 (0x0800)

Internet Protocol Version 4, Src: 10.0.1.10, Dst: 10.0.1.20 Protocol: TCP (6)

Transmission Control Protocol, Src Port: <ephemeral port>, Dst Port: 80 Flags: 0x018 (PSH, ACK)

Hypertext Transfer Protocol GET / HTTP/1.1\r\n Host: 10.0.1.20\r\n User-Agent: curl/<version>\r\n Accept: */*\r\n \r\n hint: The same capture with verbose mode and a filter for the request packet: cap -a duration:8 -V -Y "http.request"

Four layers, visible in one packet:

1. Ethernet - MAC addresses, EtherType 0x0800 (IPv4). 2. IP - Source and destination IP, protocol number 6 (TCP). 3. TCP - Source and destination ports. Port 80 is HTTP. The PSH flag means "push this data to the application." 4. HTTP - The actual request. GET method, path /, HTTP/1.1 version, and the request headers.

Each header ends with \r\n (carriage return + newline). A blank \r\n separates the headers from the body. This is the format of every HTTP message.

That last \r\n on its own line is the blank line you already met as a bare > in the curl output. Same byte sequence, two different tools, two different ways of showing it to you. Reading this dissection is the fourth of the five things your lab grades.

Milestone: the whole stack in one packet

Stop and look at what that last screen actually was. MAC addresses, an EtherType, an IP header, a protocol number, TCP ports, a PSH flag, and then a line of readable English asking for a file. Every lesson in this track, stacked inside a single frame, in the order the machine unpacks them.

You did not learn those layers separately and then imagine how they fit. You captured one real packet and read them off it, outermost first.

What is left is small: asking the same server for something else, and knowing what it says when the answer is no.

Ask for something else

The server has more than just the index page. The other file is the JSON one, and the only thing that changes is the path at the end of the URL. Leave -v off this time so that the body is all you get.

sudo ip netns exec client curl http://10.0.1.20/data.json

prompt: student@http-lab:~$ answer: sudo ip netns exec client curl http://10.0.1.20/data.json output: {"status":"ok","message":"The Linux Camp"} hint: The same curl, with /data.json on the end of the URL instead of /: sudo ip netns exec client curl http://10.0.1.20/data.json

The path in the request line changed from / to /data.json, and the server returned JSON instead of HTML. The Content-Type header in the response will say application/json instead of text/html.

This is how web APIs work. Different paths return different data. The same HTTP protocol, the same request/response cycle, but the path and content type change. Fetching a specific resource is the fifth and last of the things your lab grades.

Try adding -v to see the full request and response headers: sudo ip netns exec client curl -v http://10.0.1.20/data.json

What the server says when the answer is no

Every response carries a status code, and the code is the first thing to read when something looks wrong:

CodeMeaningWhen you see it
200OKThe request succeeded and the server returned data
301Moved PermanentlyThe resource moved to a new URL (follow the Location header)
304Not ModifiedThe cached version is still valid (no body returned)
400Bad RequestThe server could not understand your request
403ForbiddenYou do not have permission to access this resource
404Not FoundThe path does not exist on the server
500Internal Server ErrorSomething broke on the server side

If you request a path that does not exist (like /missing.html), the server answers with a 404. That is not an error in your command. It is the server telling you the resource is not there, and curl reports it as a normal, successful exchange. This particular server writes the status line as HTTP/1.0 404 File not found rather than the more common "Not Found", because the reason phrase is chosen by whoever wrote the server.

Challenge: get a 404 on purpose

Scaffolding off. No command is printed from here on.

Learn this screen in a lab rather than in an outage. Ask that server for a file it does not have, /missing.html, with the headers visible so you can read the status line it sends back.

The reply is a full verbose ex

Practice HTTP in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.

More lessons in The Application Layer