HTTP/2 is surprisingly bad at gracefully handling network interruptions, often causing clients to drop connections entirely even for minor blips.
Let’s see what nghttp can do. Imagine we have a simple web server running locally on port 8080.
# Start a simple HTTP/2 server (e.g., using Python's http.server with ssl)
# For simplicity, let's assume you have a self-signed cert named server.pem and key server.key
# python3 -m http.server 8080 --cgi --bind 127.0.0.1 --ssl server.pem server.key
Now, we can use nghttp to interact with it. First, let’s just fetch the homepage.
nghttp --no-verify https://localhost:8080/
This command, nghttp, is part of the nghttp2 suite. The --no-verify flag is crucial when dealing with self-signed certificates like we’re using for this local example; it tells nghttp to ignore certificate validation errors. Without it, you’d get an SSL handshake failure.
The output shows the HTTP/2 frames exchanged. You’ll see HEADERS frames containing the request and response headers, and DATA frames for the body. Notice the stream IDs; each request/response pair gets its own unique stream ID, which is a core concept of HTTP/2 multiplexing.
To get a more detailed view, including the raw frame types and their contents, use the -v flag.
nghttp -v --no-verify https://localhost:8080/
This will output every single HTTP/2 frame, prefixed with its type (e.g., HEADERS, DATA, SETTINGS, GOAWAY). This level of detail is invaluable for debugging. You can see the SETTINGS frame sent by the client and the server’s acknowledgment, which negotiates parameters like window sizes and maximum concurrent streams.
What if you want to send a POST request with data? nghttp can do that too.
echo "This is the request body." | nghttp --no-verify -d - https://localhost:8080/submit
Here, -d - tells nghttp to read the request body from standard input (-). The server would need to be configured to handle requests to /submit. The output will again show the frames, including the DATA frame carrying your message.
The real power for testing comes with nghttpd, the HTTP/2 server daemon. You can use it to simulate specific server behaviors or to test client resilience.
Let’s start a simple nghttpd server that serves files from the current directory.
nghttpd -v -p 8080 -d .
The -v here is for verbose logging of server-side events. -p 8080 specifies the port, and -d . indicates the document root. Now, any client connecting to https://localhost:8080/ will get files from your current directory.
To test error conditions or specific HTTP/2 features, nghttp’s --header and --data options are your friends. You can craft arbitrary requests.
nghttp --no-verify --header "X-Custom-Header: TestValue" https://localhost:8080/
This sends a GET request with a custom header. You’ll see the HEADERS frame in the output containing :method: GET, :path: /, host: localhost:8080, and your x-custom-header: TestValue.
The multiplexing aspect of HTTP/2 means multiple requests can be in flight simultaneously over a single TCP connection. nghttp can simulate this by opening multiple streams.
nghttp --no-verify "https://localhost:8080/page1" "https://localhost:8080/page2"
This sends two requests concurrently. Observe how the output interleaves frames from different streams, all using the same underlying connection. The server’s SETTINGS frame can limit how many streams are allowed concurrently.
One subtle but crucial aspect is how HTTP/2 handles flow control. Both the connection and individual streams have their own flow control windows. If a client or server doesn’t WINDOW_UPDATE these frames, traffic can stall. nghttp allows you to manually send WINDOW_UPDATE frames, though this is more for deep debugging than typical testing.
The nghttp tool is excellent for verifying that your HTTP/2 server or client is behaving according to the RFC. You can inspect the exact frames being sent and received, ensuring that headers are formatted correctly, data is transmitted as expected, and control frames like SETTINGS and WINDOW_UPDATE are being managed properly.
When debugging connection issues, look for GOAWAY frames. A GOAWAY frame from the server signals that it’s shutting down the connection, often due to an error condition or a graceful shutdown. The frame includes an error code and the last stream ID that was processed. If you see a GOAWAY with a PROTOCOL_ERROR code, it means the client sent something the server didn’t understand at the HTTP/2 protocol level.
If you’re seeing unexpected behavior with your HTTP/2 traffic, the next step is often to analyze the specific frame types and their payloads using nghttp -v.