HTTP/2

HTTP (Hyper Text Transfer Protocol) is an application-layer protocol used for communicating between a server and a client.

HTTP/2 enables a more efficient use of network resources over HTTP/1.1 by introducing header compression and allowing multiple concurrent exchanges over the same connection.

Table of contents

  1. Introduction
  2. Starting HTTP2
    1. HTTP/2 Connection Preface
  3. Streams
    1. Stream Identifiers
  4. Headers
  5. Response/ Request exchange
  6. Frames
    1. The DATA frame
    2. The HEADERS frame
    3. The PUSH_PROMISE frame
  7. Server push
  8. Flow-control
  9. Stream priority
  10. References

Introduction

HTTP/2 keeps the core semantics of HTTP/1.1. HTTP methods, status codes, URIs, and header fields are still used. What HTTP/2 changes is the format of the data, and how the data is sent between the client and the server [1, P. 207].

In HTTP/2, the HTTP data is encoded in a binary framing layer, rather than ASCII. Because of the new data format, HTTP/2 isn’t backward compatible (hence the new version) [1, P. 208].

HTTP/2 is based on Google’s SPDY protocol [1, P. 208]. It was standardized by rfc7540 in 2015.

The aim of HTTP/2 is to improve performance issues suffered by HTTP/1.x, such as repetitive headers, and inefficient use of TCP [2, Pp. 3-4].

HTTP/2 works by creating a long-lived connection between a client and a server. Clients communicate with each other over this connection by sending frames.

A frame is the basic protocol unit. There are different types of frames, like HEADERS, DATA, and SETTINGS frames.

Each HTTP request/response is associated with its own stream within an HTTP/2 connection. Streams are mostly independent of each other, so blocked streams don’t affect other streams (solving the head-of-line blocking issue that HTTP/1.x was prone to) [2, Pp. 4, 15].

HTTP/2 includes flow-control and prioritization to ensure streams are used efficiently. It also adds a new push feature, where a server can push responses to a client in response to a client-initiated request [2, P. 5].

HTTP/2 runs over a TCP connection and uses the same default ports as HTTP/1.x (80 for “http”, 443 for “https”).

Starting HTTP2

Clients that request an HTTP URI without knowing whether the server supports HTTP/2, use the HTTP upgrade mechanism. An HTTP/1.1 request is made that includes an Upgrade header field with h2c as its value (h2 for HTTP/2 over TLS) [2, P. 8].

If the server supports HTTP/2, it can accept the upgrade with a status code of 101 (switching protocols). After the 101 response, the server can send HTTP/2 responses [2, P. 9].

The first HTTP/2 frame sent by the server is a SETTINGS frame (known as a server connection preface) [2, P. 9].

A client that attempts to upgrade to HTTP/2 must include an HTTP2-Settings header field. The value will be the payload of a SETTINGS frame (Base64 encoded) containing the HTTP2 settings for the requesting client [2, P. 10].

To start an HTTP/2 connection with a server that is known to support HTTP/2, a client must first send a client connection preface to the server. After the connection preface has been sent, the client can send HTTP/2 frames to the server [2, Pp. 10-1].

HTTP/2 Connection Preface

Each endpoint must send a connection preface to confirm that the HTTP/2 protocol is being used and to set initial settings for the connection [2, P. 11].

The client connection preface in hex is 0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a. A SETTINGS frame follows the sequence, although it can be empty [2, P. 11].

Clients can send frames to the server immediately after sending the connection preface [2, P. 12].

The server connection preface is a SETTINGS frame (that can be empty) [2, P. 11].

The SETTINGS frame sent from a peer needs to be acknowledged by the receiver. This is done by sending a SETTINGS frame with the ACK flag set [2, Pp. 11,40].

Streams

A stream is a sequence of frames sent between a client and a server over an HTTP/2 connection [2, P. 15].

An HTTP/2 connection can contain multiple streams, which can be interleaved. A stream is identified by an integer, and can be closed by either endpoint [2, P. 15].

Streams go through multiple states: idle, open, half-closed, and closed [2, P. 16].

All streams start idle. Sending or receiving a HEADERS frame causes stream to become open [2, P. 17].

Sending a PUSH_PROMISE reserves an idle (local) stream. Receiving PUSH_PROMISE reserves an idle (remote) stream. A PUSH_PROMISE frame references the stream to be reserved in a Promised Stream Id field [2, P. 17].

An open stream can be used by both peers to send any kind of frame [2, P. 18].

Either peer can send a frame with the END_STREAM flag set, which puts the stream in a half-closed state [2, P. 18].

The terminal state is the closed state. Only PRIORITY frames can be sent on a closed stream [2, Pp. 19-20].

Stream Identifiers

Streams are identified with a 31-bit unsigned int. Streams initiated by a client use odd numbered values, streams initiated by a server use even values [2, Pp. 20-1].

The stream identifier must be greater than the previous stream identifiers used on the connection. Once a stream is opened, all streams initiated by the peer in the idle state with a lower stream value are closed [2, P. 21].

Stream identifiers can’t be reused. If a connection exhausts all stream identifiers, the client and server must reestablish a connection [2, P. 21].

Headers

An HTTP/2 header field (a header) is a name with an associated value. Headers are used in request, response, and server push operations [2, P. 14].

Header lists are collections of headers. A header list is serialized into a header block when transmitted, and compressed following the HPACK compression scheme [2, P. 14].

The header block is divided into one or more header block fragments, and then transmitted within the payload of a HEADERS, PUSH_PROMISE, or CONTINUATION frame [2, P. 14].

The receiving endpoint reassembles a header block by concatenating its fragments. It gets the header list by decompressing the block [2, P. 14].

Header compression is stateful. A compression context and decompression context are used for the entire HTTP connection [2, P. 14].

Header frames must be transmitted as a contiguous sequence, they can’t be interleaved with other frames. The last frame in a headers sequence has the END_HEADERS flag set [2, Pp. 14-5].

(See a list of HTTP headers for more details)

Response/ Request exchange

An HTTP request or response consists of:

  • For a response, zero or more HEADERS frames (and CONTINUATION frames) containing messages of 1xx responses.
  • One HEADERS frame followed by zero or more CONTINUATION frames.
  • Zero or more DATA frames containing the request/response body.
  • Optional HEADERS frame (and possible CONTINUATION frames) containing the trailer-part.

[2, P. 52]

A request is sent on a new stream, using an unused stream identifier. A corresponding response and request are sent on the same stream [2, P. 52].

The last frame in a sequence has its END_STREAM flag set. END_STREAM puts the stream into a half-closed (local) state for the client, and half-closed (remote) state for the server [2, Pp. 52-3].

HTTP2 uses pseudoheaders that begins with a colon (:) to convey metadata like the target URI (:path), the HTTP method (:method), and the status code of the response (:status) [2, P. 54].

HTTP/2 connections are persistent, and it’s expected that a client won’t close a connection until it determines that no further communication is necessary (e.g. if a user navigates away from a webpage). Servers should maintain connections for as long as possible, but are allowed to close connections if necessary (e.g. if a server is running low on memory) [2, P. 65].

Frames

Endpoints can send frames once a connection is established [2, P. 12].

Frames have the following format:

    +-----------------------------------------------+
    |                 Length (24)                   |
    +---------------+---------------+---------------+
    |   Type (8)    |   Flags (8)   |
    +-+-------------+---------------+-------------------------------+
    |R|                 Stream Identifier (31)                      |
    +=+=============================================================+
    |                   Frame Payload (0...)                      ...
    +---------------------------------------------------------------+

[2, P. 12]

Length is a 24-bit unsigned integer representing the length of the frame payload.

Type is the type of frame (e.g. DATA, HEADERS).

Flags contains boolean flags that are specific to the type of frame.

R is a reserved bit.

Stream Identifier is an unsigned 31-bit integer that identifies a stream.

[2, Pp. 12-3]

The Frame Payload depends on the type of frame. This section will cover the frame payloads of DATA, HEADER, and PUSH_PROMISE frames in detail.

The different types of frames are:

  • DATA: contains the application data.
  • HEADERS: used to a open a stream and (optionally) carry a headers block fragment [2, P. 32].
  • SETTINGS: used for configuring how the endpoints communicate, and to acknowledge receipt of the initial parameters [2, Pp. 36-7].
  • PUSH_PROMISE: notifies a receiving endpoint of a stream that a sending endpoint intends to initiate [2, P. 40].
  • PRIORITY: used to set priority for the stream that the frame is part of. It can be sent in any stream state [2, P. 34].
  • RST_STREAM: terminates a stream [2, P. 36].
  • PING: used for measuring the round-trip time from the sender [2, P. 42].
  • WINDOW_UPDATE: used to implement flow control [2, P. 46].

The DATA frame

DATA frames contain application data. They are associated with a stream and are used to carry the payload of HTTP requests and responses.

    +---------------+
    |Pad Length? (8)|
    +---------------+-----------------------------------------------+
    |                            Data (*)                         ...
    +---------------------------------------------------------------+
    |                           Padding (*)                       ...
    +---------------------------------------------------------------+

The Pad Length field is conditional: it only appears if the PADDED flag is set. Pad Length contains the length of the Padding.

The Data field is the application data that’s being sent.

Padding contains octets set to 0.

The DATA frame has two flags that can be set in the Flags field: END_STREAM, and PADDED.

  • END_STREAM: indicates that the frame is the last frame for the identified stream.
  • PADDED: indicates that the Pad Length field exists and Padding exists.

The length of the Data field is the length of the frame payload after subtracting the other fields.

[2, P. 31]

The HEADERS frame

HEADERS frames open streams and (optionally) carry a header block fragment [2, P. 32].

    +---------------+
    |Pad Length? (8)|
    +-+-------------+-----------------------------------------------+
    |E|                 Stream Dependency? (31)                     |
    +-+-------------+-----------------------------------------------+
    |  Weight? (8)  |
    +-+-------------+-----------------------------------------------+
    |                   Header Block Fragment (*)                 ...
    +---------------------------------------------------------------+
    |                           Padding (*)                       ...
    +---------------------------------------------------------------+

The Pad Length field is conditional: it only appears if the PADDED flag is set. Pad Length contains the length of the Padding.

E indicates that the stream dependency is exclusive (meaning this stream is the only stream dependant on a parent stream). It’s only present if the PRIORITY flag is set [2, P. 33].

Stream Dependency is a stream identifier for the stream that this stream depends on [2, P. 33].

Weight is an unsigned 8-bit integer representing the priority of the stream [2, P. 33].

Header Block Fragment contains a header block fragment [2, P. 33].

HEADERS frames have the following flags:

  • END_STREAM: puts the stream into a half-closed state.
  • END_HEADERS: indicates the frame has a header block and isn’t followed by a CONTINUATION frame [2, P. 34].
  • PADDED: indicates whether the frame contains padding.
  • PRIORITY: indicates that Exclusive Flag (E), Stream Dependency, and Weight fields are present [2, P. 34].

If a header block fragment doesn’t fit in a header frame, it’s included in a CONTINUATION frame [2, P. 34].

The PUSH_PROMISE frame

The PUSH_PROMISE frame is used to implement server push.

    +---------------+
    |Pad Length? (8)|
    +-+-------------+-----------------------------------------------+
    |R|                  Promised Stream ID (31)                    |
    +-+-----------------------------+-------------------------------+
    |                   Header Block Fragment (*)                 ...
    +---------------------------------------------------------------+
    |                           Padding (*)                       ...
    +---------------------------------------------------------------+

The Pad Length field is conditional: it only appears if the PADDED flag is set. Pad Length contains the length of the Padding.

R is a reserved bit.

Promised Stream ID is an unsigned 31-bit integer that represents the stream that is reserved by the PUSH_PROMISE frame.

Header Block Fragment is a header block fragment for the pushed response.

Padding octets are set to 0.

[2, P. 40]

Server push

Server push enables a server to preemptively send responses to a client that has made a request [2, P. 60].

Pushed responses are associated with an explicit request from a client. PUSH_PROMISE frames are sent on the stream of the associated request. A PUSH_PROMISE frame includes a promised stream identifier, created from the valid list of stream identifiers [2, P. 62].

A PUSH_PROMISE frame creates a stream in a reserved (local) state for the server and reserved (remote) state for the client using the identifier sent in the Promised Stream ID field [2, P. 62].

Once a server has sent a PUSH_PROMISE frame, it can send a response on a stream with the stream identifier sent in the PUSH_PROMISE frame.

Flow-control

The flow-control scheme ensures that streams do not interfere with each others use of the TCP connection. Flow-control is applied to both individual streams and the overall connection [2, P. 22].

Flow-control is implemented using a window that is kept by each sender on every stream. The window is a value that represents how many octets of data a sender is able to send [2, P. 47].

Currently only DATA frames are affected by flow control [2, P. 23].

There are two flow-control windows:

  1. The stream flow-control window
  2. The connection flow-control window

After a sender sends a flow-controlled frame, it will reduce both flow-control windows by the length of the sent frame [2, P. 47].

After a receiver consumes data, it sends a WINDOW_UPDATE frame with the number of octets it has freed. There are separate WINDOW_UPDATE frames for the stream-level and connection-level windows [2, P. 47].

When a sender receives a WINDOW_UPDATE frame, it increases the relevant window size by the specified length [2, P. 47].

Flow control is determined by the receiver, and is directional [2, P. 23].

The initial flow control value is 65,535 octets for both new streams and for the overall connection [2, P. 23]. These values can be configured by the SETTINGS_INITIAL_WINDOW_SIZE setting [2, P. 48].

Stream priority

Clients can assign the priority for streams by including prioritization information in the HEADERS frame [2, P. 24].

At any other point during a stream’s lifetime, the PRIORITY frame can be used to change the priority of a stream [2, P. 24].

Streams can also be marked as dependent on the completion of other streams. A stream dependant on another stream is a dependant stream. The stream that it’s dependant on is its parent stream [2, Pp. 24-5].

References

  1. [1] I. Grigorik, High-Performance Browser Networking, 1st ed. O’Reilly Media, 2013.
  2. [2] R. Belshe M. Peon and M. Thomson, “Hypertext Transfer Protocol Version 2 (HTTP/2),” no. 7540. RFC Editor, May-2015.