status
this chapter is in active development
expect live edits and rapid iteration (except for when i am really busy with other stuff) while this material is written.
status
this chapter is in active development
expect live edits and rapid iteration (except for when i am really busy with other stuff) while this material is written.
tls finished; now comes the part users actually notice.
RFC 9112 says what http/1.1 is allowed to look like, and RFC 7540 shows how we shove multiple conversations through one tcp connection without totally losing our minds.
GET / HTTP/1.1
host: example.org
user-agent: curl/8.7.1
accept: */*
method + path + version.
headers glue on metadata; host is mandatory in http/1.1, no arguments. forget it and you deserve the 400.
empty line, then body (if any) with a Content-Length or Transfer-Encoding: chunked.
no header, no body, no sympathy when proxies drop you.
status codes aren't vibes: 2xx = you did it, 3xx = go elsewhere, 4xx = you broke it, 5xx = we broke it.
headers like Cache-Control, Via, Content-Length, and Content-Type tell intermediaries how to behave; lie here and watch cdns mis-cache your entire site.
chunked encoding is just hex sizes + data ending in 0\r\n\r\n. if you mess that up, every proxy on the path treats you like hostile traffic.
http/1.0 closed every connection unless both sides agreed to Connection: keep-alive.
http/1.1 flipped the default: stay alive until somebody says Connection: close or idle timers nuke it.
run curl -v --no-keepalive https://example.org and wireshark will show fresh tcp + tls handshakes for every request.
wasteful, but educational.
http/2 still rides port 443 but frames everything into numbered streams and compresses headers via hpack (RFC 7541).
browsers negotiate it via alpn; curl literally prints ALPN, server accepted to use h2.
it is faster until packet loss hits, because the whole thing still sits on tcp and inherits head-of-line blocking.
chapter 3 is where quic/http/3 finally cut that anchor.
userland uses send()/write() to hand buffers to the kernel; kernel encrypts and tosses them on the wire.
recv()/read() pull decrypted data back up.
short reads are normal - the kernel gives you whatever it has. don't whine, loop until you hit Content-Length or eof.
want receipts? strace -e sendto,recvfrom curl -o /dev/null https://example.org.
> lines show bytes leaving, < lines show responses. here is http/2:
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://example.org/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: example.org]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.14.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: example.org
> User-Agent: curl/8.14.1
> Accept: */*
>
< HTTP/2 200
< content-type: text/html
< etag: "bc2473a18e003bdb249eba5ce893033f:1760028122.592274"
< cache-control: max-age=86000
< content-length: 513
< alt-svc: h3=":443"; ma=93600
http/2 uses pseudo-headers (:method, :path, :authority) instead of a request line. stream [1] is the first request on this connection. alt-svc advertises http/3 on the same port - a quic-capable client might switch to udp next time.
force --http1.1 and the wire format changes:
* using HTTP/1.x
> GET / HTTP/1.1
> Host: example.org
> User-Agent: curl/8.14.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/html
< Cache-Control: max-age=86000
< Content-Length: 513
< Connection: keep-alive
< Alt-Svc: h3=":443"; ma=93600
http/1.1 sends GET / HTTP/1.1 as plaintext. Connection: keep-alive is explicit here; http/2 does not need it because multiplexing handles connection reuse differently.
same server, same response, different framing. the bytes on the wire are not the same even though curl prints similar output.
everything below http was plumbing.
this is the layer where missing headers, stale cookies, and weird transfer encodings take prod down.
know exactly what you put on the wire so "the network" stops getting blamed for application bugs.
once you can read these traces, tracing a request shows how to stitch the whole timeline together.