Skip to content

HTTP Protocol

Three versions of HTTP are in production use: HTTP/1.1, HTTP/2 and HTTP/3. All three carry the same semantics — methods, status codes, headers, bodies — and differ only in how those semantics are framed and moved across the wire.

One request is in flight per connection at a time. A client that wants concurrency has to open several connections, and the number it can hold open is bounded by the browser’s per-origin limit, by the operating system’s socket and file-descriptor limits, and ultimately by the ephemeral port range.

The cost of that model is connection setup — a TCP handshake, and a TLS handshake on top of it — repeated for every connection. The benefit is isolation: if one connection stalls or fails, the others are unaffected. Headers travel as plain text, so a proxy or packet capture can read them without any protocol-specific tooling.

Requests and responses share a single connection, which is logically divided into independent streams. This removes the per-request connection cost and the head-of-line blocking that HTTP/1.1 pipelining suffered from at the application layer, though a lost TCP segment still stalls every stream on the connection, because TCP delivers bytes in order.

Headers are compressed with HPACK rather than sent as text. Compression is not encryption: packet-capture tools decode HPACK, and Wireshark exposes the decoded fields. What actually hides an HTTP/2 exchange from a capture is TLS, which applies to HTTP/1.1 over TLS equally — in both cases the session keys are needed to see anything at all.

RFC 9114, published in June 2022, maps HTTP semantics onto QUIC instead of TCP. QUIC runs over UDP and implements streams, per-stream flow control, loss recovery and TLS 1.3 itself, so a lost packet stalls only the stream it belonged to rather than the whole connection, and a resumed connection can send application data on the first flight. Header compression uses QPACK, a variant of HPACK adapted to out-of-order delivery.

Because QUIC is UDP-based, middleboxes and firewalls that only expect TCP/443 will block it. Servers therefore advertise HTTP/3 from an HTTP/1.1 or HTTP/2 response — via the Alt-Svc header or an HTTPS DNS record — and clients fall back when UDP is unavailable.

Application-specific headers should be given plain, descriptive names. RFC 6648 deprecated the X- prefix in 2012: it was meant to mark a name as experimental, but headers never stop being experimental in practice, and the prefix ends up permanently welded to names that have become standard.

Names we use, and what they are for:

HeaderPurpose
DebugRaise log verbosity for the handling of this one request
ExperimentSelect an experimental code path for A/B testing
TestOperate against test data rather than customer data
Request-IdCorrelate a request across every node that handles it
TimestampMeasure per-hop processing time and network latency

Headers that switch behaviour — Debug, Experiment, Test — must be authorised, not merely honoured. An unauthenticated header that puts a service into test mode is a way for a caller to read or write the wrong data.