Originally published at https://blog.pathvector.dev/protocol-lab-http-27/ — part of the free Protocol Lab series.
This post is part of Protocol Lab, a free, hands-on series for learning networking protocols by building and breaking them in a container lab. All the lab material — topologies, configs, and scripts — lives in the repo: github.com/pathvector-studio/protocol-lab.
In Lab #10 we read one HTTP request and one response, in the clear. This lab adds the two mechanisms that make the web feel stateful even though HTTP is not: redirects (the server sends the client somewhere else) and cookies (the server hands the client a token to send back on every future request).
Reading guide: rfc-notes/http-redirects-cookies.md
Prerequisite: HTTP Lab 10: One Exchange, Read in the Clear
Expected time: 40–55 minutes.
The Goal
You will drive both mechanisms with curl:
-
GET /oldreturns 302 Found with aLocation: /newheader — "go here instead". - With
curl -L, the client follows the redirect and ends up at/new. -
GET /newsendsSet-Cookie: session=abc123— the server hands out a token. - The client stores it and resends it, so
GET /whoamishows the server gotCookie: session=abc123back.
By the end, you should be able to label this table:
| Request | Response | Effect |
|---|---|---|
GET /old |
302 Found, Location: /new
|
client is told to go to /new
|
GET /new (after -L) |
200, Set-Cookie: session=abc123
|
client stores the cookie |
GET /whoami (with -b) |
200, body echoes Cookie: session=abc123
|
server sees the cookie |
What You Will Learn
- What a 3xx redirect is, and the role of the
Locationheader. - The difference between a client that follows redirects and one that does not.
- What
Set-CookieandCookieare, and how they create a "session" on top of stateless HTTP. - Why cookies are how logins, carts, and preferences persist across requests.
- That the server is otherwise stateless — it only knows you by the cookie you send.
This lab does not cover:
- The differences between 301/302/303/307/308 in depth (we use 302).
- Cookie security attributes (
Secure,HttpOnly,SameSite) beyond naming them. - Server-side session storage, JWTs, or OAuth.
Where to Read in the RFCs
| RFC | Section | What to focus on |
|---|---|---|
| RFC 9110 | 15.4 | The meaning of 3xx (Redirection) and the Location header |
| RFC 9110 | 10.2.2 | How Location is used |
| RFC 6265 | 3 | The syntax of the Set-Cookie and Cookie headers |
| RFC 6265 | 4.1, 5.2 | Cookie attributes (Path, Secure, HttpOnly, and friends) |
The Big Picture
One client, one server. The server is a small Python app.
client (10.0.0.1) ------ eth1/eth1 ------ server (10.0.0.2:8080)
curl /old -> 302 Location: /new
/new -> 200 Set-Cookie: session=abc123
/whoami -> echoes the Cookie back
sequenceDiagram
participant C as client (curl)
participant S as server
C->>S: GET /old
S-->>C: 302 Found, Location: /new
Note over C: -L: follow the redirect
C->>S: GET /new
S-->>C: 200, Set-Cookie: session=abc123
Note over C: -c: store the cookie in a jar
C->>S: GET /whoami (Cookie: session=abc123)
S-->>C: 200, "you sent Cookie: session=abc123"
10.0.0.0/24 is a local, closed range.
Note: Everything here runs in local address space, so nothing in this lab touches the real internet.
What You Need
Recommended environment:
- Linux / WSL2 / a Linux VM
- Docker
- containerlab
Images used:
-
nicolaka/netshoot:latest— bundles bothcurlandpython3.
No additional images are required. The server is examples/http-27/server/app.py (standard library only).
Running the Lab
The quick path, which deploys, verifies, and tears down for you:
./scripts/labctl.sh run http-27
Or step through it manually:
1. Move into the working directory
cd protocol-lab/examples/http-27
2. Deploy and start the server
sudo containerlab deploy -t http-27.clab.yml
docker exec -d clab-http-27-server python3 /app/app.py
3. Watch the redirect (without following, then following)
# don't follow: see the raw 302 and Location header
docker exec clab-http-27-client curl -sD - -o /dev/null http://10.0.0.2:8080/old
# follow: -L takes you all the way to /new
docker exec clab-http-27-client curl -sL -w "\n[final] %{url_effective}\n" http://10.0.0.2:8080/old
HTTP/1.1 302 Found
Location: /new
...
[final] http://10.0.0.2:8080/new
4. Watch the cookie round trip
# /new returns Set-Cookie; -c saves it to a cookie jar
docker exec clab-http-27-client curl -sD - -o /dev/null -c /tmp/jar.txt http://10.0.0.2:8080/new
# send the cookie to /whoami with -b; the server echoes it
docker exec clab-http-27-client curl -s -b /tmp/jar.txt http://10.0.0.2:8080/whoami
Set-Cookie: session=abc123; Path=/
...
you sent Cookie: session=abc123
Expected Output
-
GET /old:HTTP/1.1 302 FoundwithLocation: /new. - The final URL after following with
-L:.../new. -
GET /new:Set-Cookie: session=abc123. -
GET /whoami(with the cookie attached): the body containsCookie: session=abc123.
Why It Works
HTTP is fundamentally stateless. Each request is independent, and the server remembers nothing about previous requests. Yet on the web, logins persist and shopping carts survive page loads — thanks to two mechanisms: redirects and cookies.
-
Redirects (3xx + Location). The server tells the client "not that URL — look over here instead."
302 Foundmeans "temporarily somewhere else." The destination goes in theLocationheader, not the response body. The client (a browser, orcurl -L) reads it and automatically re-requests the new URL. This is what powers post-login page transitions, http→https upgrades, and moved URLs. -
Following is the client's job. The server only points at the destination; whether to actually go there is up to the client.
curldoes not follow by default (it shows you the raw 302);-Lmakes it follow. Browsers follow automatically. -
Cookies (Set-Cookie / Cookie). The server attaches
Set-Cookie: name=valueto a response, handing the client a small token. The client stores it and, from then on, automatically attachesCookie: name=valueto every request to the same server. The server looks at that value and recognizes "this is the client from before." That is how a "session" is built on top of stateless HTTP. -
The server only knows you by your cookie. There is no memory of "you" on the server side (the app in this lab stores nothing at all). The cookie the client sends is the only clue. That is why deleting your cookies logs you out — and why a stolen cookie lets someone impersonate you (hence the
Secure/HttpOnly/SameSiteattributes that protect it).
The key insight: HTTP is stateless, but by steering the client with redirects and giving it a token to send back with cookies, it can behave as if it has state.
Common Pitfalls
-
Thinking the server takes you to the redirect target. The server only supplies
Location. Following it is the client's job (-L, or a browser). -
Looking for the 302 destination in the body. The destination lives in the
Locationheader, not the body. - Thinking the server remembers the cookie. The client does the remembering. The server just reads whatever cookie is sent.
-
Confusing
-cand-b.-csaves to the cookie jar;-bsends from it. Browsers do both automatically. -
Dismissing cookie attributes.
Secure/HttpOnly/SameSitematter for security (this lab keeps things minimal). - The 3xx family. 301 (permanent), 302 (temporary), 307/308 (method-preserving), and more — pick by use case.
Cleanup
sudo containerlab destroy -t http-27.clab.yml --cleanup
If you used labctl.sh run http-27, the script runs destroy for you at the end.
Check Your Understanding
- What does a server return in a 3xx redirect? Which header carries the destination?
- Who follows a redirect? What flag makes
curlfollow one? -
Set-CookieandCookie— who sends each one, and to whom? - HTTP is stateless, so why can a "session" persist?
- How does the server identify you? What happens if you delete the cookie?
- What are the cookie attributes
Secure/HttpOnly/SameSitefor?
References
- RFC 9110: HTTP Semantics (Redirection 3xx, Location)
- RFC 6265: HTTP State Management Mechanism (Cookies)
- RFC 5737: IPv4 Address Blocks Reserved for Documentation
- curl manual page
Verified Run Log (2026-07-07)
This lab has been confirmed reproducible on real hardware.
Environment:
- Ubuntu 26.04 LTS (kernel 7.0.0-27-generic, x86_64)
- Docker 29.1.3
- containerlab 0.77.0
- client / server:
nicolaka/netshoot:latest(curl/python3; the server runsserver/app.py)
Running PATH="/tmp/pl-shim:$PATH" ./scripts/labctl.sh run http-27 performed deploy → verify → destroy, and verification.json returned "status": "verified".
The redirect (302 + Location, then following it)
$ curl -sD - -o /dev/null http://10.0.0.2:8080/old
HTTP/1.1 302 Found
Location: /new
$ curl -sL -w "[final] %{url_effective}\n" http://10.0.0.2:8080/old
[final] http://10.0.0.2:8080/new
GET /old returns 302 Found with Location: /new. curl -L follows it and lands on /new. The server only points at the destination — the client does the following.
The cookie round trip (Set-Cookie → Cookie)
$ curl -sD - -o /dev/null -c jar.txt http://10.0.0.2:8080/new
Set-Cookie: session=abc123; Path=/
$ curl -s -b jar.txt http://10.0.0.2:8080/whoami
you sent Cookie: session=abc123
GET /new hands out Set-Cookie: session=abc123, and the client (-c) stores it. Sending it to GET /whoami with -b, the server echoes back that it received Cookie: session=abc123. HTTP is stateless, but by turning the cookie into a "token you must send back," the server can identify the client across requests — and that is all a session really is.
Cleanup
containerlab destroy -t http-27.clab.yml --cleanup
That's redirects and cookies: the server points, the client follows, and a little token sent back on every request is the entire illusion of state on the web.
Explore the full Protocol Lab series here: github.com/pathvector-studio/protocol-lab. If these labs are useful to you, please ⭐ star the repo on GitHub — it genuinely helps others find the project.
Next up, we'll layer more onto HTTP — think caching, content negotiation, and what happens when TLS enters the picture.
Top comments (0)