cadlag dot org

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:

Full Trace

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.

Settings and More

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:

  1. 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).
  2. The client (running on port 54081) sends a segment that appears to have three frames: Magic, SETTINGS, and WINDOW_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 a SETTINGS frame. The WINDOW_UPDATE frame is used for flow control.1
  3. 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:

Headers and Data

This looks like what we expect:

  1. The first GET request is carried by a HEADERS frame from the client to the server.
  2. The server responds. The response has two parts: a HEADERS frame (e.g. carrying the 200 OK code) and a DATA frame with the actual body of the response.
  3. Since our script makes two responses back-to-back, we see another pair of HEADERS (from the client) followed by HEADERS+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.

Reply to this post by email ↪