HTTP/2: The view from Wireshark
Last time we talked about HTTP/2 header compression. Today we’ll actually look at HTTP/2 in action, using Wireshark.
For what follows, we’ll assume a simple client & server interaction
where the client makes two GET
requests over a single, persistent
HTTP/2 connection. At first I tried to do this in Python using
hyper-h2 but after fussing
around a bit I just gave up and switched to Go. The Go
net/http package supports HTTP/2 and it
was pretty easy to have ChatGPT write a simple
client
and
server
for me. The client makes two GET /
requests, and the server responds
with "hello world"
to each. The only thing I had to tweak was the
http.Transport
settings used by the client, to force HTTP/2 over a
single connection.
When I ran this, the resulting Wireshark capture looked like this:
To start with, we see the same sort of TLS negotiation that we discussed previously. After this we see the HTTP/2 messages.
The first few of these, indicated below, come before the actual GET
request.
Since this was run on a loopback interface, distinguishing the source and destination of the packets can be tricky. The easiest way to identify these is to note that there is a TCP ACK (in the opposite direction) immediately following each. With that in mind, here’s what we see:
- The server (running on port 8443) sends the client a segment with a
single
SETTINGS
frame. The HTTP/2 spec requires that the server send this to confirm that the protocol is in use and establish initial settings for the HTTP/2 connection (this is known as the connection preface). - The client (running on port 54081) sends a segment that appears
to have three frames:
Magic
,SETTINGS
, andWINDOW_UPDATE
.Magic
indicates the string"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
which starts the client’s connection preface (this was chosen so that HTTP/1.1 intermediaries would not attempt to process further frames). Following this is aSETTINGS
frame. TheWINDOW_UPDATE
frame is used for flow control.1 - Following this, both the client and the server respond with a
SETTINGS
frame to acknowledge that the previous settings have been received.
After this exchange, the connection is ready for the actual requests & responses. I indicate these below:
This looks like what we expect:
- The first
GET
request is carried by aHEADERS
frame from the client to the server. - The server responds. The response has two parts: a
HEADERS
frame (e.g. carrying the200 OK
code) and aDATA
frame with the actual body of the response. - Since our script makes two responses back-to-back, we see another
pair of
HEADERS
(from the client) followed byHEADERS
+DATA
from the server.
Looking closely, we can see HPACK in action. The size of the
first HEADERS
packet from the client is 145 bytes. But the second
time around this is only 113 bytes. The actual header is the same, but
the second time around the client can reference previously sent
values, reducing the transmitted data size.
HTTP/2 is undoubtedly harder to work with than HTTP/1.1, but much of the friction will ease up as tooling matures. With first-class support for HTTP/2, Wireshark makes debugging and understanding the protocol a breeze.
-
See also this StackOverflow discussion. ↩︎