In HTTP/2, a stream is a request/response interaction. According to the HTTP/2 Standard, each stream is created in state idle, and goes through a couple of states until it eventually reaches closed.

                     send PP |        | recv PP
                    ,--------|  idle  |--------.
                   /         |        |         \
                  v          +--------+          v
           +----------+          |           +----------+
           |          |          | send H /  |          |
    ,------| reserved |          | recv H    | reserved |------.
    |      | (local)  |          |           | (remote) |      |
    |      +----------+          v           +----------+      |
    |          |             +--------+             |          |
    |          |     recv ES |        | send ES     |          |
    |   send H |     ,-------|  open  |-------.     | recv H   |
    |          |    /        |        |        \    |          |
    |          v   v         +--------+         v   v          |
    |      +----------+          |           +----------+      |
    |      |   half   |          |           |   half   |      |
    |      |  closed  |          | send R /  |  closed  |      |
    |      | (remote) |          | recv R    | (local)  |      |
    |      +----------+          |           +----------+      |
    |           |                |                 |           |
    |           | send ES /      |       recv ES / |           |
    |           | send R /       v        send R / |           |
    |           | recv R     +--------+   recv R   |           |
    | send R /  `----------->|        |<-----------'  send R / |
    | recv R                 | closed |               recv R   |
    `----------------------->|        |<----------------------'

       send:   endpoint sends this frame
       recv:   endpoint receives this frame

       H:  HEADERS frame (with implied CONTINUATIONs)
       PP: PUSH_PROMISE frame (with implied CONTINUATIONs)
       ES: END_STREAM flag
       R:  RST_STREAM frame

Since version v0.0.11, h2c implements a h2c stream-info command that can be used to view the stream states on the client. This post shows some examples of the command.

Run the Echo Server

We will re-use the Echo Server from the Server Push Demo to run the stream-info examples below. The Echo Server takes a POST request, and then sends the received data back to the client using a Server Push message.

Download and compile the Echo Server as follows:

git clone
cd http2-examples/jetty-http2-echo-server
mvn package

In order to run it, learn the correct ALPN version for your JDK (see table 14.1), and download the ALPN boot JAR from Maven Central. With the right <path-to-alpn-boot.jar>, run the Echo Server as follows:

java -Xbootclasspath/p:<path-to-alpn-boot.jar> \
    -jar target/jetty-http2-echo-server.jar

Example Script

The h2c stream-info command is intended to be used when debugging a server, where we can set breakpoints and go through each interaction step-by-step. However, if we put a bunch of h2c stream-info commands into a shell script, they are usually executed fast enough to see the state transitions. Create a shell script ./ with the following commands (the example runs on Linux and OS X. For Windows use cygwin):


# Create a 66k file in /tmp/data
dd if=/dev/zero of=/tmp/data bs=1024 count=66

h2c connect localhost:8443
h2c post --file /tmp/data /data > /dev/null &
h2c stream-info
h2c stream-info
h2c stream-info
# ... repeat `h2c stream-info` a couple of times

Note that the post command is run in the background, because if we wait for the post to be finished, we will only see streams in state closed.

Start h2c like this:

h2c start --dump

In another shell window, run ./

Stream States for the POST request

The output shows stream states for two streams:

  • The POST request with stream ID 1.
  • The Server Push message with stream ID 2.

Let’s analyze the stream states for the POST request with stream ID 1 first:

1: POST /data open
1: POST /data half closed (local)
1: POST /data closed

We cannot observe the initial idle state, because when the stream is created, it switches to open immediately sending the request header. When the request body is sent (DATA frame with END_STREAM), the state becomes half closed (local), indicating that the client is done with its part of the request/response interaction.

Receiving the response moves the stream state to closed.

Stream States for the Server Push message

As described in the Server Push Demo, the Echo Server sends the posted data back using a Server Push message. The Server Push message consists of three parts sent from the server to the client:

  • PUSH_PROMISE to indicate that the server will send a “promised” response for the URL /data.
  • The header of the “promised” response.
  • The body of the “promised” response.

The corresponding state transitions for stream ID 2 are as follows:

2: GET /data reserved (remote) (cached push promise)
2: GET /data half closed (local) (cached push promise)
2: GET /data closed (cached push promise)

Again, we cannot observe the initial idle state, because when the stream is created upon receiving the PUSH_PROMISE, the stream goes immediately from idle to reserved (remote). Receiving the header moves the stream state to half closed (local), receiving the body moves the state to closed.

Note that the “promised” stream goes from idle to closed only through receiving frames, there is no sent frame involved.


The h2c stream-info command provides a way to view HTTP/2 stream states on the h2c client. This is particularly useful for debugging server applications.