The original address: blog. Wangriyu. Wang / 2018/05 – HTTP…

HTTP/2 HTTP/2 HTTP/2

Wiki

RFC 7540 defines the protocol specifications and details of HTTP/2. The details of this article are mainly from this document. It is recommended to read this article first, then go back to the RFC for a brief overview of the protocol, and then take a closer look at the RFC if you want to dig into some of the details

RFC7540

Why use it ?

Problems with HTTP/1.1:

1. Limit the number of TCP connections

A browser can create a maximum of six to eight TCP connections for the same domain name. In order to solve the quantity limitation, domain name sharding technology emerged, which is actually resource domain division. Resources are placed under different domain names (such as secondary sub-domain name), so that connections and requests can be created for different domain names, which can break the limitation in a clever way. However, abuse of this technology will also cause many problems. For example, each TCP connection itself needs to go through DNS query, three-step handshake, slow start, etc., but also occupy extra CPU and memory, for the server too many connections are also easy to cause network congestion, traffic congestion, etc., for mobile terminal problems are more obvious, you can refer to this article: Why Domain Sharding is Bad News for Mobile Performance and Users

In the figure, you can see that six new TCP connections are created, and each new connection takes time for DNS resolution (ranging from several ms to several hundred ms), TCP slow start takes time, TLS handshake takes time, and subsequent requests have to wait for queue scheduling

2. Head Of Line Blocking

Each TCP connection can process only one request-response at a time. The browser processes requests according to the FIFO principle. If the previous response is not returned, subsequent request-response will be blocked. Pipelining-pipelining technology has been developed to solve this problem, but pipelining has many problems. For example, the first response is slow or blocks subsequent responses, the server needs to cache multiple responses in order to return the response, the server may have to reprocess multiple requests if the browser disconnects and retries in mid-stream, and the client-proxy-server must support pipelining

3, Header content, and each request Header will not change too much, there is no corresponding compression transmission optimization scheme

4. In order to reduce the number of requests as much as possible, it is necessary to merge files, Sprite images, inline resources and other optimization work, but this undoubtedly leads to the problem that the content of a single request becomes larger and the delay becomes higher, and the embedded resources cannot effectively use the caching mechanism

5. Plaintext transmission is not secure

Advantages of HTTP2:

1. Binary Framing Layer

Frame is the smallest unit of data transmission. The original plaintext transmission is replaced by binary transmission. The original message is divided into smaller data frames:

Comparison of H1 and H2 packets:

In the figure, the h2 packet is reconstructed and parsed. It can be found that some header fields are changed, and all the header fields are lowercase

strict-transport-security: max-age=63072000; IncludeSubdomains fields allow the server to enable the HSTS policy to force the browser to use HTTPS for communication, reducing the risk of additional requests and session hijacking caused by redirects

To enable HSTS on the server, take Nginx as an example, add add_header strict-transport-security “max-age=63072000 to the server module of the corresponding site. includeSubdomains” always; Can be

In Chrome, you can open Chrome ://net-internals/# HSTS to enter the HSTS management interface of the browser. You can add/delete/query HSTS records, as shown in the following image:

During the HSTS period and the TLS certificate is still valid, a browser visiting blog.wangriyu.wang will automatically add https:// without having to do a query to redirect to HTTPS

For frames see: How does It Work? Frame –

Multiplex (MultiPlexing)

On a TCP connection, we can continuously send frames to each other. The Stream identifier of each frame identifies which stream the frame belongs to, and then, when received by the other party, we can concatenate all frames of each stream according to the Stream identifier to form a whole block of data. With HTTP/1.1 treating each request as a stream, multiple requests become multiple streams, the request response data is divided into multiple frames, and frames from different streams are sent to each other interleaved. This is multiplexing in HTTP/2.

The flow concept implements multiple request-response parallelism on a single connection, solves the problem of wire head blocking, and reduces the number of TCP connections and the problems caused by slow TCP connection startup

So http2 only needs to create one connection for the same domain, instead of 6~8 connections as HTTP /1.1 does:

See: How does it Work? – flow

3. Server Push

The browser sends a request, and the server actively pushes resources associated with the request to the browser so that the browser doesn’t have to make subsequent requests.

Server-push is mainly optimized for resource inlining, and has advantages over HTTP /1.1 resource inlining:

  • The client can cache pushed resources
  • Clients can reject pushed resources
  • Push resources can be shared by different pages
  • The server can push resources according to priority

See: How does it Work? – Server-Push

4. Header Compression (HPACK)

Use HPACK algorithm to compress header content

For details on HPACK see: How does it Work? – HPACK

5. Resetting connections at the application layer

For HTTP/1, this is done by setting a reset flag in the TCP segment to tell the peer end to close the connection. In this way, the connection is disconnected and must be re-established the next time a request is made. HTTP/2 introduces a frame of type RST_STREAM, which can do a better job of canceling a request stream if the connection is constantly open.

6. Request priority setting

Dependencies and weights can be set for each stream in HTTP/2, and priority can be assigned by Dependency tree, preventing critical requests from being blocked

7. Flow control

Each HTTP2 stream has its own public traffic window that restricts the other end from sending data. For each stream, each end must tell the other that it has enough room to process new data, and the other end is only allowed to send so much data until the window is enlarged.

See: How does it Work? – Flow control

Several optimizations of HTTP/1 can be deprecated

Merging files, inlining resources, Sprite images, and domain sharding is not necessary for HTTP/2. Use H2 to fine-grained resources as much as possible and split files as much as possible without worrying about the number of requests

How does it work ?

The Frame to Frame

The structure of the frame

All frames have a fixed 9-byte header (payload) and a payload of a specified length (payload):

+-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | Type (8) | Flags (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============================================================+ | Frame Payload (0...) . +---------------------------------------------------------------+Copy the code
  • LengthRepresents the length of the entire frame, expressed as a 24-bit unsigned integer. Data length should not exceed 2^14(16384) bytes unless the receiver sets a larger value on SETTINGS_MAX_FRAME_SIZE (size can be any value between 2^14(16384) bytes and 2^24-1(16777215) bytes). The 9 bytes of the header don’t count
  • TypeDefines the frame type, expressed in 8 bits. The frame type determines the format and semantics of the frame body, and should be ignored or discarded if type is unknown.
  • FlagsIs a Boolean identifier reserved for frame type correlation. Flags assign different semantics to different frame types. If the flag does not define semantics for a frame type, it must be ignored and sent with a value of (0x0)
  • RIs a reserved bit. The semantics of this bit are undefined, and it must be set to (0x0) when sent and ignored when received.
  • The Stream Identifier is used for flow control and is represented by a 31-bit unsigned integer. Sids established by the client must be odd, siDs established by the server must be even, and values (0x0) are reserved for frames associated with the entire connection (connection control messages) rather than individual streams
  • Frame PayloadIs the body content, determined by the frame type

There are ten types of frames:

  • HEADERS: header frame (type=0x1), used to open a stream or carry a header block fragment
  • DATA: DATA frame (type=0x0), which fills the body information. One or more DATA frames can be used to return the response body of a request
  • PRIORITY: PRIORITY frame (type=0x2), which specifies the stream PRIORITY suggested by the sender. PRIORITY frames can be sent in any stream state, including idle and closed streams
  • RST_STREAM: stream termination frame (type=0x3), which is used to request cancellation of a stream, or to indicate that an error has occurred. The payload carries an error code of a 32-bit unsigned integer.Error CodesYou cannot send RST_STREAM frames on idle streams
  • SETTINGS: Set frame (type=0x4), set thisThe connectionIs applied to the entire connection
  • PUSH_PROMISE: Push frame (type=0x5), the server push frame, the client can return a RST_STREAM frame to select the stream to reject push
  • PING: PING frame (type=0x6) to determine if an idle connection is still available, and also to measure minimum round trip time (RTT)
  • GOAWAY: GOWAY frame (type=0x7), which is used to initiate a request to close a connection or to warn of a critical error. GOAWAY stops receiving new streams and finishes processing previously established streams before closing the connection
  • WINDOW_UPDATE: Window update frame (type=0x8), used to perform flow control, either on a single Stream (specifying a Stream Identifier) or on the entire connection (Stream Identifier 0x0). Only the DATA frame is affected by flow control. After the flow window is initialized, the flow window is reduced as much as the load is sent. If the flow window is insufficient to send, the WINDOW_UPDATE frame can increase the flow window size
  • CONTINUATION: Continuation frame (type=0x9), used to continue the first block fragment sequence, seeHead compression and decompression

The DATA frame format

 +---------------+
 |Pad Length? (8)|
 +---------------+-----------------------------------------------+
 |                            Data (*)                         ...
 +---------------------------------------------------------------+
 |                           Padding (*)                       ...
 +---------------------------------------------------------------+
Copy the code
  • Pad Length😕 If the Padding length is specified, it means that the Padding flag is set
  • Data: Data transmitted. The maximum length of the data is equal to the length of the frame payload minus the length of other fields
  • Padding: Padding byte, which has no specific semantics and must be set to 0 when sent to confuse the packet length, similar to CBC block encryption in TLS. For details, see CBC block encryption in TLSHttpwg.org/specs/rfc75…

DATA frames have the following flags:

  • END_STREAM: bit 0 Set to 1 to represent the last frame of the current stream
  • PADDED: Bit 3 being set to 1 indicates the presence of Padding

Example:

HEADERS frame format

 +---------------+
 |Pad Length? (8)|
 +-+-------------+-----------------------------------------------+
 |E|                 Stream Dependency? (31)                     |
 +-+-------------+-----------------------------------------------+
 |  Weight? (8)  |
 +-+-------------+-----------------------------------------------+
 |                   Header Block Fragment (*)                 ...
 +---------------------------------------------------------------+
 |                           Padding (*)                       ...
 +---------------------------------------------------------------+
Copy the code
  • Pad Length: Specifies the Padding length. If it exists, the Padding flag is set
  • E: A bit that specifies whether the stream’s dependencies are exclusive. If they are, the PRIORITY flag is set
  • Stream Dependency: Specifies a stream identifier that represents the ID of the stream on which the current stream depends. If the stream identifier exists, the PRIORITY flag is set
  • Weight: An unsigned 8 is an integer, representing the PRIORITY weight value (1 to 256) of the current flow. If it exists, the PRIORITY flag is set
  • Header Block Fragment: Header block fragment
  • PaddingPadding byte: Padding byte, no specific semantics, same as DATA Padding, if the Padding flag exists, it means that the Padding flag is set

HEADERS frames have the following flags:

  • END_STREAM: Bit 0 is set to 1 to indicate that the current header block is the last to be sent, but a HEADERS frame with the END_STREAM flag can also be followed by a CONTINUATION frame.
  • END_HEADERS: bit 2 If this parameter is set to 1, the header block ends
  • PADDED: Bit 3 being set to 1 indicates that the Pad is set, including Pad Length and Padding
  • PRIORITY: bit 5 Set to 1 to indicate Exclusive Flag (E), Stream Dependency, and Weight

Example:

Head compression and decompression

The header field in HTTP/2 is also a key with one or more values. These header fields are used for HTTP request and response messages and also for server push operations.

A Header List is a collection of zero or more Header fields. When transmitted over a connection, the Header list is serialized into a Header Block by a compression algorithm (HPACK below). The serialized first Block is then divided into one or more sequences of bytes called Header Block fragments and payload delivered via HEADERS, PUSH_PROMISE, or CONTINUATION frames.

Cookie Header Field requires special treatment for HTTP mapping, see 8.1.2.5. Compressing the Cookie Header Field

There are two possibilities for a complete header

  • A HEADERS or PUSH_PROMISE frame with the END_HEADERS flag set
  • A HEADERS frame or PUSH_PROMISE frame with no END_HEADERS flag set, plus multiple CONTINUATION frames, the last of which has the END_HEADERS flag set

The header block must be passed as a sequence of consecutive frames and cannot be inserted into any other type or stream of frames. Setting the END_HEADERS flag to represent the end of the head block makes the head block logically equivalent to a single frame. The receiver joins the fragment and reassembles the first block, then decompresses the first block to reconstruct the first list.

SETTINGS frame format

Httpwg.org/specs/rfc75…

The payload of a SETTINGS frame consists of zero or more parameters. Each parameter has the following format:

 +-------------------------------+
 |       Identifier (16)         |
 +-------------------------------+-------------------------------+
 |                        Value (32)                             |
 +---------------------------------------------------------------+
Copy the code
  • Identifier: represents the parameter type, for example, SETTINGS_HEADER_TABLE_SIZE is 0x1
  • Value: Indicates the value of the parameter

At the beginning of the connection, each party sends a SETTINGS frame to indicate what it expects the other party to do. The other party accepts the configuration parameters and returns an empty SETTINGS frame with an ACK flag to indicate confirmation. At any time after the connection, either party may send a SETTINGS frame again. Parameters in the SETTINGS frame are overwritten by the latest parameters received

The SETTINGS frame applies to the entire connection, not to a stream, and the Stream identifier of the SETTINGS frame must be 0x0, otherwise the recipient will treat it as a PROTOCOL_ERROR.

The SETTINGS frame contains the following parameters:

  • SETTINGS_HEADER_TABLE_SIZE (0x1): Size of the Header compression table used to parse Header blocks, starting at 4096 bytes
  • SETTINGS_ENABLE_PUSH (0x2): Server Push can be disabled. This value is initially 1 to allow Server Push
  • SETTINGS_MAX_CONCURRENT_STREAMS (0x3): Represents the maximum number of streams that the sender allows the receiver to create
  • SETTINGS_INITIAL_WINDOW_SIZE (0x4): The initial value is 2^ 16-1 (65535) bytes. The maximum value is 2^ 31-1. If the maximum value is exceeded, FLOW_CONTROL_ERROR is returned
  • SETTINGS_MAX_FRAME_SIZE (0x5): Indicates the maximum number of bytes a sender is allowed to receive. The initial value is 2^14(16384) bytes. If this value is not between the initial value (2^14) and the maximum value (2^ 24-1), return PROTOCOL_ERROR
  • SETTINGS_MAX_HEADER_LIST_SIZE (0x6): Indicates the maximum number of bytes in the size of the header list that the sender intends to receive. This value is based on the size of the uncompressed header field, including the byte length of the name and value, plus 32 bytes of overhead for each header field

The SETTINGS frame has the following flags:

  • ACK: If bit 0 is set to 1, the recipient has received the SETTINGS request and agrees to the SETTINGS. The SETTINGS frame payload must be empty

Example:

The actual packet capture will find that the HTTP2 request creates a connection and sends the SETTINGS frame initialization with a Magic frame (the preampt for establishing the HTTP/2 request).

In HTTP/2, both ends are required to send a connection preface as final confirmation of the protocol used and to determine the initial setup of the HTTP/2 connection, with the client and server each sending a different connection preface.

The client’s preface (corresponding to frame 23 in the figure above) contains a sequence of PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n \r\n + a SETTINGS frame that can be empty, Application data sent after receiving a 101(Switching Protocols) response (representing upgrade success), or as the first transfer of a TLS connection. If an HTTP/2 connection is enabled with prior knowledge that the server supports HTTP/2, the client connection preface is sent when the connection is established.

The server preface (frame 26 in the figure above) contains an empty SETTINGS frame that is sent as the first frame after an HTTP/2 connection is established. See HTTP/2 Connection Preface for more details

After sending the prologue, each party sends the other a SETTINGS frame with an ACK identifier, corresponding to frames 29 and 31 in the figure above.

Request the entire sequence of frames for the site, with the number after the frame representing the ID of the stream to which it belongs, and finally close the connection with a GOAWAY frame:

A GOAWAY frame with the largest stream identifier (such as frame 29 in the figure, which is the largest stream) will continue to process streams up to that number for the sender before actually closing the connection

Flow Stream –

A Stream is simply a logical concept that represents a separate bidirectional sequence of frames exchanged between the client and server in an HTTP/2 connection, and each frame’s Stream Identifier field specifies which Stream it belongs to.

Streams have the following properties:

  • A single H2 connection can contain multiple concurrent streams, and the two ends can cross send frames of different streams
  • Streams can be created and consumed unilaterally by clients or servers, or shared
  • Streams can be closed by either side
  • The order in which frames are sent on the Stream is very important, and eventually the receiver reassembles the frames with the same Stream Identifier into a complete message message

The state of flow

Note that the Send and RECV objects in the diagram refer to endpoints, not the current stream

idle

All streams start in the “idle” state. In this state, no frames are exchanged

Its state transition:

  • Sending or receiving a HEADERS frame is idleidleFlow becomes openopenThe Stream Identifier field of the HEADERS frame specifies the Stream ID. Similarly, a HEADERS frame (with END_STREAM) can make a stream half-closed immediately.
  • The server must be open in oneopenOr partially closed (remote)half-closed(remote)The Promised Stream ID field of the PUSH_PROMISE frame specifies a Promised new Stream (initiated by the server).
    • The new stream will be idle on the serveridleState enter reserved (local)reserved(local)state
    • The new stream will be idle on the client sideidleThe state enters the reserved (remote)reserved(remote)state

A special case is described in 3.2-starting HTTP/2 for “HTTP” URIs:

The client initiates an HTTP/1.1 request with the Upgrade mechanism to create an H2C connection, and the server agrees to the Upgrade and returns a 101 response. HTTP/1.1 requests sent before the upgrade are assigned a stream identifier 0x1 and given a default priority value. From client to server this flow 1 implicitly goes to the “half-closed” state because it has already been completed as an HTTP/1.1 request. After an HTTP/2 connection is started, stream 1 is used to respond. See HTTP/2 protocol negotiation mechanism below for details

A frame receiving other than HEADERS and PRIORITY is treated as a PROTOCOL_ERROR in this state

In the state diagram, send PP and RECV PP indicate that both endpoints of the connection sent or received PUSH_PROMISE, not that an idle stream sent or received PUSH_PROMISE. It is the advent of PUSH_PROMISE that causes a promised stream to change from idle to reserved

The Server Push content and the use of PUSH_PROMISE are described in more detail in server-push below

reserved (local) / reserved (remote)

The flow indicated by PUSH_PROMISE changes from idle to this state, indicating that a Server push is ready

Its state transition:

  • A PUSH_PROMISE frame starts a stream response with HEADERS frame, which immediately places the stream half-closed (remote) on the server.half-closed(remote)State, placed in half closed on client (local)half-closed(local)State, and ends with a frame carrying END_STREAM, which places the stream closedclosedstate
  • Either endpoint can send a RST_STREAM frame to terminate the stream, whose state is determined byreservedtoclosed

A reserved(local) stream cannot send a frame other than HEADERS, RST_STREAM, or PRIORITY. A received frame other than RST_STREAM, PRIORITY, or WINDOW_UPDATE is PROTOCOL_ERROR

A reserved(remote) stream cannot send frames other than RST_STREAM, WINDOW_UPDATE, or PRIORITY. A stream receiving frames other than HEADERS, RST_STREAM, or PRIORITY is PROTOCOL_ERROR

open

A stream in the open state can be used by two peers to send any type of frame

Its state transition:

  • Either end can send a frame with the END_STREAM identifier, which the sender will turn inhalf-closed(local)State; The receiver will transferhalf-closed(remote)state
  • Either end can send a RST_STREAM frame, which causes the stream to enter immediatelyclosedstate
half-closed (local)

A flow is bidirectional. Semi-closed indicates that the flow is closed in one direction. Local indicates that the flow is closed from the local end to the peer end

Streams in this state cannot send frames other than WINDOW_UPDATE, PRIORITY, or RST_STREAM

A stream in this state becomes closed when it receives a frame with the END_STREAM identifier or when either party sends an RST_STREAM frame

Streams in this state receive PRIORITY frames to adjust the order of stream dependencies, see stream PRIORITY below

half-closed (remote)

Streams in this state are not used by the peer to send frames, and the endpoint performing flow control is no longer obligated to maintain the flow control window for the receiver.

An endpoint receiving frames other than WINDOW_UPDATE, PRIORITY, or RST_STREAM on a stream in this state should respond to a STREAM_CLOSED stream error

A flow in this state can be used by an endpoint to send any type of frame, and the endpoint still observes flow control limits at the flow level in this state

A stream in this state becomes closed when it sends a frame with the END_STREAM identifier or when either party sends an RST_STREAM frame

closed

The representative stream is closed

Streams in this state cannot send frames other than PRIORITY, which adjusts the PRIORITY of streams that depend on the closed stream. Endpoints should process PRIORITY frames, although PRIORITY frames can also be ignored if the stream is removed from the dependency tree

A WINDOW_UPDATE or RST_STREAM frame may be received for a short period after receiving a DATA or HEADERS frame with an END_STREAM identifier. This is because the remote peer may send RST_STREAM or frames with the END_STREAM flag before receiving and processing these types of frames. But the endpoint must ignore any WINDOW_UPDATE or RST_STREAM received

If a stream enters this state after sending RST_STREAM frames, and the peer end may have sent or is in the sending queue when receiving RST_STREAM frames, these frames are irrevocable and must be ignored by the endpoint that sent RST_STREAM frames.

An endpoint can limit the length of a period, frames accepted within that period are ignored, and frames beyond that period are considered an error.

An endpoint that sends RST_STREAM frames and receives flow control frames (such as DATA) is still counted in the flow window, even though these frames are ignored because the peer end must have sent flow control frames before receiving RST_STREAM frames, and the peer end will assume that flow control is in effect

An endpoint may receive a PUSH_PROMISE frame after sending an RST_STREAM frame. A PUSH_PROMISE frame makes a promised stream reserved even if it has been reset. Therefore, RST_STREAM is needed to close an unwanted foreshadowing stream.

PRIORITY frames can be sent and received by streams of any state. Frames of unknown type are ignored

Flow state transitions

Let’s look at two examples to understand flow:

(1) The Server sends a PUSH_PROMISE frame on a Promised Stream, and the Promised Stream ID specifies a Promised Stream for the Client to push. After send PP, the flow changes from idle to Reserve (Local) on the server. After recV PP on the client, the flow changes from idle to Remote

(3) At this time, the stream is in the reserved state. If the client chooses to reject the push, it can send an RST frame to close the stream. The server can also send an RST frame to cancel push if something goes wrong. This state changes to Closed regardless of which party sends or receives an RST

(4) If no reset occurs, the push is still valid. Then the server starts to push and sends the HEADERS block first, and the flow state changes to half-closed(remote). Half-closed (local) After the client receives HEADERS

A half-closed stream should continue to push frames such as DATA and CONTINUATION frames, and if either party initiates a reset, the stream will be closed

(7) If all goes well and the resource completes with the data frame, the last frame will be marked with END_STREAM indicating that the stream is closed

(1) The client sends a HEADERS frame and its Stream Identifier creates a new Stream that changes from idle to open

(2)(3) If the client cancels the request, it can send the RST frame, and the server can also send the RST frame if there is an error. No matter which party receives or sends the RST, the stream is closed and enters the closed state;

(4) If the request ends (END_STREAM), the flow is half-closed. In the case of a GET request, the HEADERS frame is usually the last frame, and the flow immediately enters the semi-closed state after sending H. In the case of a POST request, the last frame is marked with END_STREAM and the flow is half closed

(6) After the client is half closed, the server starts to return a response. At this time, either party receives or sends RST and the stream is closed.

(7) If all goes well, wait for the response to end (END_STREAM) and the stream is closed

The identifier of the stream

The flow ID is a 31-bit unsigned integer, the flow initiated by the client must be odd, the flow initiated by the server must be even, and 0x0 is reserved for connection control messages that cannot be used to establish new flows.

When HTTP/1.1 is upgraded to HTTP/2, the response stream ID is 0x1. After the Upgrade is completed, the client state 0x1 will be half-closed (local), so the client cannot initialize a stream with 0x1

The ID of a newly created stream must be greater than all used numbers. An ID of error size received should return a PROTOCOL_ERROR response

Using a new stream implicitly disables a stream whose ID is smaller than that of the current stream and is in idle state. For example, a stream sends a HEADERS frame to a stream whose ID is 7 but has never sent a frame to a stream whose ID is 5. The stream 0x5 becomes closed after 0x7 sends or receives the first frame

The flow ID within a connection cannot be reused

The priority of the stream

The client can specify the PRIORITY of a newly established stream using the PRIORITY information of the HEADERS frame, and can also send PRIORITY frames to adjust the stream PRIORITY during other periods

The purpose of setting priorities is for an endpoint to express how it expects the peer side to allocate resources between concurrent streams. More importantly, when sending capacity is limited, priority can be used to select the stream to send the frame.

Streams can be marked as dependent on other streams, which are then processed for the current stream. Each dependency is followed by a weight, a number used to determine the relative proportion of allocator resources that depend on the same flow

Stream Dependencies

Each flow can explicitly depend on another, and including dependencies means that resources are allocated to the specified flow (the upper-layer node) in preference to the dependent flow

A stream that is not dependent on another stream will specify a stream dependency value of 0x0 because the non-existent stream 0x0 represents the root of the dependency tree

A stream that depends on another stream is called a dependent stream, and the dependent stream is the parent of the current stream. If the dependent stream is not in the current dependency tree (for example, if the state is idle), the dependent stream uses a default priority

Streams that share the same parent level are not ordered relative to each other. For example, if A dependency stream D is added to B and C, the order of BCD is not fixed:

    A                 A
   / \      ==>      /|\
  B   C             B D C
Copy the code

Exclusive allows a new level (a new dependency) to be inserted. Exclusive causes this stream to become the only dependent stream of the parent and other dependent streams to become its children. For example, a new dependent stream E with exclusive is also inserted:

                      A
    A                 |
   /|\      ==>       E
  B D C              /|\
                    B D C
Copy the code

In a dependency tree, a dependent flow should be allocated resources only if all the streams it depends on (a chain with a parent of up to 0x0) are shut down or cannot continue to execute on it

Depend on the weight

All dependent streams are assigned a weight value of 1 to 256

Dependent flows of the same parent level allocate resources according to the weight ratio. For example, if flow B depends on A and the weight value is 4, and flow C depends on A and the weight value is 12, when A stops executing, theoretically, B can allocate only one third of C’s resources

Reprioritization

Use PRIORITY frames to adjust stream priorities

The content of the PRIORITY frame is the same as that of the HEADERS frame:

 +-+-------------------------------------------------------------+
 |E|                  Stream Dependency (31)                     |
 +-+-------------+-----------------------------------------------+
 |   Weight (8)  |
 +-+-------------+
Copy the code
  • If the parent resets the priority, the dependent flow moves with its parent. A reprioritized stream with an exclusive identifier causes all children of the new parent stream to depend on that stream

  • If a stream is adjusted to depend on one of its children, the dependent child is first moved below the parent of the stream (that is, the same level), and the entire subtree of that stream is moved, keeping the weights of the moved dependencies

Look at the following example: The first graph is the initial relationship tree, and now A is adjusted to depend on D. According to the second point, D is now moved below X, and A is adjusted to be A subtree of D (Figure 3). If A is adjusted with an exclusive identifier, F is also classified as A subtree according to the first point (Figure 4).

    x                x                x                 x
    |               / \               |                 |
    A              D   A              D                 D
   / \            /   / \            / \                |
  B   C     ==>  F   B   C   ==>    F   A       OR      A
     / \                 |             / \             /|\
    D   E                E            B   C           B C F
    |                                     |             |
    F                                     E             E
               (intermediate)   (non-exclusive)    (exclusive)
Copy the code
State management of flow priorities

When a stream is removed from the dependency tree, its children can be adjusted to depend on the parent of the closed stream. The new dependency weights are recalculated based on the weight of the closed stream and the weight of the stream itself.

Removing streams from the dependency tree causes some priority information to be lost. Resources are shared between streams with the same parent, which means that if a stream in the collection is shut down or blocked, any free capacity is allocated to the nearest adjacent stream. However, if the common dependencies of this collection (that is, the parent nodes) are removed from the tree, these subflows will share resources with the flows at a higher level

An example: Streams A and B depend on the same parent node, while streams C and D both depend on A. If neither A nor D can execute for A period of time before removing stream A (perhaps the task is blocked), C will allocate all resources to A. If A is removed from the tree, the weight of A is reassigned to C and D, and D still blocks, and C allocates less resources than before. For the same initial weight, C gets 1/3 of the available resources instead of 1/2 (why 1/3? There are no details in the document, and it is not clear how the weight is reassigned. The following is explained according to my understanding.)

The resource of X is 1, the initial weight of ABCD is 16, and * indicates that the node is currently unavailable. In Figure 1, C and B each account for half of the resource, while the weight of CD is reassigned to 8 after A is removed. Therefore, in Figure 2, the proportion of C and B changes to 1:2, and R(C) changes to 1/3

(v: 1.0 X) X (v: 1.0) / \ | \ \ / A = B = > / | | \ * \ (w: 16) (w: 16) / | \ \ B/C * D \ (8) w: (w: 8) (16) w: C * D (w: 16) (w:16) R(C)=16/(16+16)=1/2 ==> R(C)=8/(8+16)=1/3Copy the code

It is possible that the priority information for creating dependencies to a stream is still in transit and that stream is already closed. If a dependent flow’s dependency points to no relevant priority information (that is, the parent node is invalid), the dependent flow will be assigned a default priority, which may result in an undesirable priority because the flow is assigned an unexpected priority.

To avoid these problems, an endpoint should retain the prioritization status of a flow for a period of time after the flow is closed. The longer this status is retained, the less likely it is that the flow will be assigned an incorrect or default priority.

Similarly, a stream in the “idle” state can be assigned priority or become the parent of another stream. This allows grouping nodes to be created in the dependency tree for a more flexible priority expression. Idle streams start with the default priority

The retention of stream priority state information can add to the burden on the end, so this state can be restricted. The terminal may decide the number of additional states to retain depending on the load; Under high loads, additional priority states can be discarded to limit resource tasks. In extreme cases, an endpoint can even discard priority information for the active or reserved state flow. If a fixed limit is used, an endpoint should retain at least as much stream state as the SETTINGS_MAX_CONCURRENT_STREAMS setting

Default priority

All streams are initially non-exclusive and depend on stream 0x0.

The Pushed flow initially relies on the associated flow (see server-push).

In both cases, the weight of the stream is specified as 16.

Server-Push

PUSH_PROMISE frame format

 +---------------+
 |Pad Length? (8)|
 +-+-------------+-----------------------------------------------+
 |R|                  Promised Stream ID (31)                    |
 +-+-----------------------------+-------------------------------+
 |                   Header Block Fragment (*)                 ...
 +---------------------------------------------------------------+
 |                           Padding (*)                       ...
 +---------------------------------------------------------------+
Copy the code
  • Pad Length: Specifies the Padding length. If it exists, the Padding flag is set
  • R: reserved 1bit
  • Promised Stream ID: an unsigned 31-bit integer representing the stream reserved by the PUSH_PROMISE frame. The stream identifier must be a valid value for the next stream for the sender
  • Header Block Fragment: contains the first block fragment of the request header field
  • PaddingPadding byte: Padding byte, no specific semantics, same as DATA Padding, if the Padding flag exists, it means that the Padding flag is set

PUSH_PROMISE frames have the following flags:

  • END_HEADERS: If the value of 2 is set to 1, the header block ends
  • PADDED: Bit 3 set to 1 indicates that the Pad is set, including Pad Length and Padding

Push the process of

Combine the flow state transitions on Server-push mentioned above

PUSH_PROMISE frames can only be sent on streams initiated by the peer (client) that are in the open or half-closed (remote) state

A PUSH_PROMISE frame is intended to push a response that is always associated with a request from the client. The server sends a PUSH_PROMISE frame on the request stream. The PUSH_PROMISE frame contains a Promised Stream ID selected from one of the Stream identifiers available to the server.

If a server receives a request for a document that contains embedded links to multiple image files, and the server chooses to push those additional images to the client, sending a PUSH_PROMISE frame before sending a DATA frame containing the image link ensures that the client finds the embedded link before sending it. Knowing that a resource is about to be pushed. Similarly, if the server intends to push a response referenced by the first block (for example, in the Link header field), sending a PUSH_PROMISE frame before sending the first block ensures that the client does not request those resources again

Once the client receives the PUSH_PROMISE frame and chooses to receive the pushed response, the client should not make any requests for the response to be pushed until the promised stream is closed.

Note that each of the four resources in this figure represents a Promised Stream ID, and that the source that sends the PUSH_PROMISE frame is still on the Stream Identifier (Stream Identifier = 1). When a client receives a PUSH_PROMISE frame and selects to receive, it does not request any of the four resources. After that, the server initiates an implied stream and pushes a resource-related response

If, for whatever reason, the client decides not to receive a response from the server ready to push, or if the server is taking too long to prepare to send the anticipated response, the client can send an RST_STREAM frame using either the CANCEL or REFUSED_STEAM code. And references the pushed stream identifier.

Nginx configuration Server – a Push

Server-push requires server setup. It does not mean that the browser initiates a request and the server automatically pushes the resource associated with the request

Nginx, for example, officially supports HPPT2 serverPush as of version 1.13.9.

Add the http2_push field to the server or location module and add the relative path to the file to push the relevant resource when requesting the resource. For example, my blog is set as follows:

  server_name  blog.wangriyu.wang;
  root /blog;
  index index.html index.htm;

  location = /index.html {
    http2_push /css/style.css;
    http2_push /js/main.js;
    http2_push /img/yule.jpg;
    http2_push /img/avatar.jpg;
  }
Copy the code

The Push response can be viewed from the browser console:

You can also test the push response with NGHTTP (* indicates that it is pushed by the server):

The http2_push setup above works for static resources, where the server knows in advance which files the client needs and pushes them selectively

If it is a background application dynamically generated file (such as JSON file), the server does not know what to push in advance, can use the Link response header to do automatic push

Add http2_push_preload on to server module;

  server_name  blog.wangriyu.wang;
  root /blog;
  index index.html index.htm;

  http2_push_preload on;
Copy the code

Then set the response header (add_header) or the daemon to return the data file with the response header Link tag, for example

Link: </style.css>; as=style; rel=preload, </main.js>; as=script; rel=preload, </image.jpg>; as=image; rel=preload
Copy the code

Nginx actively pushes these resources based on the Link response header

Introducing HTTP/2 Server Push with Nginx 1.13.9

Potential problems with Server-push

Looking at the Server Push discussion in HTTP/2 of this article, I saw a potential problem with server-push

When server-push meets the conditions, it will initiate Push, but the client has cache and wants to send RST to reject, and the Server has pushed resources before receiving RST. Although this part of Push is invalid, it will definitely occupy bandwidth

For example, in my blog about http2_push configuration, every time I open the home page Server pushes those four files, but in fact the browser knows that it has a local cache, that is, the Server’s server-push is only playing a role in bandwidth consumption while the local cache is valid

Of course it doesn’t really affect my small site that much, but if your site needs a lot of Push, you need to consider and test whether server-push affects subsequent visits

In addition, the server can set the Cookie or Session to record the access time, and then determine whether the subsequent access needs to Push; Also, the client can limit the number of PUSH streams, or it can set a low traffic window to limit the size of the data PUSH sends

As for which resources need to be pushed, there are several policies mentioned in the Authoritative Guide to Web Performance. For example, Apache’s mod_spdy recognizes the X-associated -Content header, which lists the resources that you want the server to push. In addition, someone on the Internet has made middleware based on Referer header to deal with server-push; Or servers can be smarter about documents and decide whether or not to push resources based on current traffic. I believe there will be more implementation and application of Server-push in the future

Flow control

Multiplexed streams compete for TCP resources, which leads to the flow being blocked. The flow control mechanism ensures that flows on the same connection do not interfere with each other. Flow control applies to a single flow or to the entire connection. HTTP/2 provides flow control by using WINDOW_UPDATE frames.

Flow control has the following characteristics:

  • Flow control is connection-specific. Both levels of flow control are located between the endpoints of a single hop, rather than the entire end-to-end path. For example, if there is a front-end proxy in front of a server such as Nginx, there will be two connections, browser-nginx, nginx-server, and flow control respectively. For details see: How is HTTP/2 hop-by-hop flow control accomplished? – stackoverflow
  • Flow control is based on WINDOW_UPDATE frames. The receiver announces how many bytes it intends to receive for each stream and for the entire connection. This is a credit – based scheme.
  • Flow control is directional and under the overall control of the receiver. Recipients can set arbitrary window sizes for each stream and for the entire connection. The sender must respect the traffic control limits set by the receiver. The client, the server, and the intermediary agent independently publish their own flow control Windows when they are receivers, and comply with the peer flow control Settings when they are senders.
  • The initial value of the flow control window, whether for a new stream or for the entire connection, is 65535 bytes.
  • The type of frame determines whether flow control applies to the frame. Currently, only DATA frames are affected by flow control; all other types of frames do not consume space in the flow control window. This ensures that important control frames are not blocked by flow control.
  • Flow control cannot be disabled.
  • HTTP/2 only defines the format and semantics of WINDOW_UPDATE frames. It does not specify how recipients decide when to send frames, what values to send, or how senders choose to send packets. The implementation can choose any algorithm that meets the requirements.

WINDOW_UPDATE frame format

+-+-------------------------------------------------------------+
|R|                Window Size Increment (31)                   |
+-+-------------------------------------------------------------+
Copy the code

Window Size Increment indicates the number of bytes that can be transmitted by the sender in addition to the existing flow control Window. The value ranges from 1 to 2^ 31-1 bytes.

WINDOW_UPDATE frames can be for a stream or for an entire connection. If the former, the WINDOW_UPDATE frame’s stream identifier specifies the affected stream; In the latter case, a stream identifier of 0 means that it applies to the entire connection.

The flow control function applies only to identified frames that are affected by flow control. Of the frame types defined by the document, only DATA frames are affected by flow control. Frames that are not affected by flow control must be received and processed unless the receiver can no longer allocate resources to process these frames. If the receiver can no longer receive frames, it can respond with a flow error or connection error of type FLOW_CONTROL_ERROR.

WINDOW_UPDATE can be sent by the peer that sent a frame with the END_STREAM flag. This means that a receiver may receive a WINDOW_UPDATE frame on a half-closed (remote) or closed stream and cannot treat it as an error.

Flow control window

The flow control window is a simple integer value that specifies the number of bytes of data allowed to be sent by the sender. The window value measures the caching capacity of the receiving end.

When a DATA frame is received, it must always be subtracted from the flow control window (excluding the length of the frame header, and for both levels of control Windows) unless it is considered a connection error. This is necessary even if the frame has an error, because the sender has already counted the frame in the flow control window. If the receiver has not done so, the flow control window of the sender and receiver will be inconsistent.

The sender cannot send a frame that is affected by flow control and whose length exceeds the available space of the two levels of flow control window notified by the receiver. Even if there is no room available for either level of flow control Windows, you can send a frame of length 0 with the END_STREAM flag set (that is, an empty DATA frame).

A WINDOW_UPDATE frame can be sent when the receiver of the frame consumes data and frees up space in the flow control window. For flow level and connection level flow control Windows, you need to send WINDOW_UPDATE frames respectively.

When a new connection is created, the initial window size for both the stream and connection is 2^ 16-1 (65535) bytes. You can change the initial window size of a stream by setting the SETTINGS_INITIAL_WINDOW_SIZE parameter of the SETTINGS frame in the connection preface, which applies to all streams. The initial window size of the connection cannot be changed, but the flow control window can be changed with a WINDOW_UPDATE frame, which is why the connection preface usually has a WINDOW_UPDATE frame.

In addition to changing the flow control window for streams that are not yet active, SETTIGNS frames can also change the size of the initial flow control window for streams that are already active (streams in the open or half-closed (remote) state). That is, when the value of SETTINGS_INITIAL_WINDOW_SIZE changes, the receiver must adjust the value of flow control Windows for all the streams it maintains, whether they are previously open or not.

Changing SETTINGS_INITIAL_WINDOW_SIZE may cause the available space of the flow control window to become negative. The sender must track the negative flow control window and cannot send a new DATA frame until it receives a WINDOW_UPDATE frame that makes the flow control window positive.

For example, if the client sends 60KB of data as soon as the connection is established and the server sets the initial window size to 16KB, the client will recalculate the available flow control window to -44KB as soon as it receives the SETTINGS frame. The client keeps the flow control window negative until the WINDOW_UPDATE frame restores the window value to positive and the client can continue sending data.

If changing SETTINGS_INITIAL_WINDOW_SIZE causes the flow control window to exceed its maximum value, one end must treat this as a connection error of type FLOW_CONTROL_ERROR

If the receiver wishes to use a flow control window smaller than the current value, it can send a new SETTINGS frame. However, the receiver must be prepared to receive data beyond the value of this window, because the sender may have sent data beyond the value of this smaller window before receiving the SETTIGNS frame.

Use flow control wisely

Flow control is defined to protect endpoint operations under resource constraints. For example, an agent needs to share memory between many connections, and it is possible to have slow upstream connections and fast downstream connections. Flow control addresses situations where a receiver cannot process data on one stream but still wants to continue processing other streams in the same connection.

Deployments that do not require this feature can advertise a flow control window of the maximum size (2^ 31-1), and can maintain this window size unchanged by sending a WINDOW_UPDATE frame when any data is received. This effectively disables flow control on the receiving side. In contrast, the sender is always controlled by the limitations of the flow control window advertised by the receiver.

Scheduling under resource constraints, such as memory, can use traffic to limit the amount of memory a peer can consume. It is important to note that enabling flow control when the latency product of bandwidth is not known may result in an optimal utilization of available network resources (RFC1323).

Even with a full understanding of the current network delay product, the implementation of flow control can be complex. When using flow control, the receiver must read data from the TCP receive buffer in a timely manner. Doing so can cause deadlocks in keyframes such as WINDOW_UPDATE when HTTP/2 is not available. But flow control can ensure that constrained resources can be protected without reducing connection utilization.

HTTP/2 protocol negotiation mechanism

Unencrypted negotiation -H2c

Clients use the HTTP Upgrade mechanism to request upgrades. The HTTP2-Settings header field is a header field dedicated to the connection. It contains parameters (Base64 encoded) for managing HTTP/2 connections, assuming that the server will accept the Upgrade request

GET/HTTP/1.1 Host: server.example.com Connection: Upgrade, http2-settings Upgrade: H2C http2-settings: <base64url encoding of HTTP/2 SETTINGS payload>Copy the code

If the server supports HTTP /2 and agrees to the upgrade, the protocol is converted, otherwise ignored

HTTP / 1.1101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
Copy the code

There is a potential stream 0x1 on the client that becomes half-closed after completing the H1 request, and the server returns the response with this stream

Notice that the first response stream in the figure is 0x1, as described above

At present, browsers only support HTTP/2 communication under TLS encryption, so the above situation is not possible in browsers at present. The figure shows the request initiated by the NGHTTP client

Encryption negotiation mechanism – h2

In TLS encryption, protocol negotiation is performed through ALPN during client-Hello and server-Hello

Application layer Protocol negotiation In the TLS handshake extension, in Client Hello, the ALPN Next Protocol specified by the Client is h2 or HTTP /1.1, indicating the Protocol supported by the Client

If the Server selects the H2 extension in Server Hello, it indicates that the negotiation protocol is H2 and the subsequent request response changes. If the server is not set up for HTTP /2 or does not support H2, continue to communicate with HTTP /1.1

Analysis of the instance

196: TLS Handshake The first step is Client Hello, and the protocol negotiation starts with Session Ticket

200: Server Hello agrees to use H2 and the client’s session ticket is valid. The session is resumed and the handshake is successful

202: The client also recovers the session and encrypts subsequent messages

205: The server initiates a connection (SETTINGS) frame that sets the maximum number of parallel streams, the initial window size, the maximum frame length, and then (WINDOW_UPDATE) expands the window size

The client also sends a connection to Magic and initializes SETTINGS. The SETTINGS frame sets the HEADER TABLE size, the initial window size, the maximum number of parallel streams, and then (WINDOW_UPDATE) increases the window size

311: GET/(HEADERS[1]). This HEADERS frame also contains END_STREAM. This immediately changes stream 1 from idle to half-closed(local).

311: This message also contains an ACK SETTINGS frame that the client sends to the server

312: Server also responds to SETTINGS frame with ACK

321: The server sends four PUSH_PROMISE frames on stream 1 (state half-closed(remote)). These frames reserve streams 2, 4, 6, and 8 for subsequent PUSH_PROMISE frames.

321: This message also returns the response (HEADERS DATA) from the above request. Finally, DATA is added with END_STREAM, and stream 1 changes from half-closed to closed

329: Adjust stream priority, dependency: 8 -> 6 -> 4 -> 2 -> 1 (all with exclusive flag, and the weight is 110)

342: After stream 1 is closed, stream 2 gets allocated resources, the server starts pushing, and the DATA is returned by two DATA frames

344: Stream 2 ends and stream 4 begins to be pushed

356: Adjust dependencies

1 1 1 1(w: 110) | | | | 2 2 2 2(w: 110) | | | | 4 ==> 4 ==> 6 ==> 6(w: 147) | | | | 6 8 4 8(w: 147) | | | | 8 6 8 4(w: 110).Copy the code

367, 369, 372: Push stream data of 6 and 8

377: Initiates a request, opening stream 3, where client initiated requests are dependent on stream 0x0

The request-response process is the same and ends with a GOAWAY frame closing the connection

HPACK algorithm

HTTP/2 is Here, Let’s Optimize!

The HTTP2 header uses key-value pairs, and the request and status lines in HTTP1 are split into key-value pairs. All keys are lowercase, unlike HTTP1. In addition, there is also an index space containing static index table and dynamic index table. The actual transmission will compress the header key table, using the algorithm HPACK, whose principle is to match the index space existing in the current connection. If a key value already exists, the corresponding index will replace the header entry, such as “:method: “GET” matches index 2 in the static index, and only one byte containing 2 is transmitted. If the index space does not exist, it is transmitted by character encoding. Character encoding can choose Huffman encoding, and then determine whether it needs to be stored in the dynamic index table

The index table

Static index

Static Table Definition – RFC 7541 Static Table Definition – RFC 7541 Static Table Definition – RFC 7541 Static Table Definition – RFC 7541

For example, the first few are as follows

The index The field values The key value
index Header Name Header Value
1 :authority
2 :method GET
3 :method POST
4 :path /
5 :path /index.html
6 :scheme http
7 :scheme https
8 :status 200
Dynamic index

A dynamic index table is a space-limited table maintained by a FIFO queue that contains indexes of non-static tables. Dynamic index tables are maintained by both sides of the connection, and their contents are based on the connection context. An HTTP2 connection has one and only one dynamic table. When a header does not match the index, you can choose to insert it into the dynamic index table. The next time a value with the same name may be found in the table and replaced. However, not all of the header keys are stored in the dynamic index because the dynamic index table has a space limit. The maximum value is set by SETTINGS_HEADER_TABLE_SIZE (default 4096 bytes) in the SETTING frame

  • Mysql > alter Table Size;

The size of a dynamic index table is the sum of all entries. The size of each entry = field length + key value length + 32

This extra 32 bytes is the estimated cost of an entry that uses two 64-bit Pointers to a field and a key, and two 64-bit integers to count the number of references to the field and key

Golang implementation also added 32: golang.org/x/net/http2…

The SETTING frame specifies the maximum dynamic table size, but the encoder can select a value smaller than SETTINGS_HEADER_TABLE_SIZE as the effective dynamic table load

  • How do I update the maximum size of a dynamic index table

Change the maximum dynamic table size by sending a dynamic table size update signal:

+---+---+---+---+---+---+---+---+
| 0 | 0 | 1 |   Max size (5+)   |
+---+---------------------------+
Copy the code

The prefix 001 indicates that this byte is the dynamic table size update signal, followed by the integer N=5 encoding method to indicate the new maximum dynamic table size (cannot exceed SETTINGS_HEADER_TABLE_SIZE). The calculation method is described below.

It should be noted that this signal must be sent before the first block is sent or at the interval between the two first blocks. An update signal with Max size 0 can be sent to clear the existing dynamic table

  • When does a dynamically indexed table need to expel entries
  1. Each time a table size update is signaled, the entry at the end of the queue, the old index, needs to be determined and expelled until the current size is less than or equal to the new capacity
  2. Each time a new entry is inserted, the entry at the end of the queue needs to be determined and expelled until the current size is less than or equal to the capacity. Inserting a new entry larger than Max size in this case would not be considered an error, but the result would be to empty the dynamic index table

For more information on how to manage dynamic indexed tables, see the implementation of Golang: golang.org/x/net/http2… The process can be better understood through the code

Indexed address space

An indexed address space can be composed of static and dynamic indexed tables:

<---------- Index Address Space ----------> <-- Static Table --> <-- Dynamic Table --> +---+-----------+---+ + + -- -- -- -- -- -- -- -- -- -- -- -- -- - + -- -- -- + | 1 |... | s | |s+1| ... | | s + k + + -- -- -- -- -- -- -- -- -- -- -- -- -- - + - + + + -- -- -- -- -- -- -- -- -- -- -- -- -- - + - + ⍋ | | ⍒ Insertion Point Dropping PointCopy the code

When a new key is inserted into the dynamic index table, the queue is inserted from index 62. Therefore, the dynamic index table stores the new key and the old key in descending order

Encoding type representation

HPACK encodings use two primitive types: unsigned variable-length integers and octet character strings, which specify the following encodings accordingly

Integer coding

An integer encoding can be used to represent a field index value, a header entry index value, or a string length. An integer encoding consists of two parts: a prefix byte and an optional sequence of followed bytes, which are required only if the prefix byte is insufficient to express an integer value. The available bit N in the prefix byte is an argument to the integer encoding

For example, the following is an integer encoding of N=5 (the first three bits are used for other identifiers). If we want to encode an integer value less than 2^ n-1, we simply use a prefix byte, such as 10. 01010 said

+ - + - + - + - + - + - + - + - + |? |? |? | Value | +---+---+---+-------------------+Copy the code

If the integer value X to be encoded is greater than or equal to 2^ n-1, the available bits of the prefix byte are all set to 1, and then X is subtracted from 2^ n-1 to obtain the value R, which is represented by one or more byte sequences in which the most significant bit (MSB) of each byte is used to indicate whether to end, MSB, set to 0, is the last byte. See the pseudo-code and examples below for details

+ - + - + - + - + - + - + - + - + |? |? |? | 1 1 1 1 1 | +---+---+---+-------------------+ | 1 | Value-(2^N-1) LSB | +---+---------------------------+ ... +---+---------------------------+ | 0 | Value-(2^N-1) MSB | +---+---------------------------+Copy the code

Code:

if I < 2^N - 1, encode I on N bits
else
    encode (2^N - 1) on N bits
    I = I - (2^N - 1)
    while I >= 128
         encode (I % 128 + 128) on 8 bits
         I = I / 128
    encode I on 8 bits
Copy the code

Decoding:

decode I from the next N bits
if I < 2^N - 1.return I
else
    M = 0
    repeat
        B = next octet
        I = I + (B & 127) * 2^M
        M = M + 7
    while B & 128= =128
    return I
Copy the code

For example, use the integer N=5 encoding to represent 1337:

If 1337 is greater than 31 (2^ 5-1), fill the last five prefix bytes with 1

I = 1337 – (2^5 – 1) = 1306

I is still greater than 128, I % 128 = 26, 26 + 128 = 154

Binary code: 10011010, which is the first heel byte

I = 1306/128 = 10, I is less than 128, end of loop

Encode I in binary: 00001010, which is the last byte

+---+---+---+---+---+---+---+---+
| X | X | X | 1 | 1 | 1 | 1 | 1 |  Prefix = 31, I = 1306
| 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 |  1306 >= 128, encode(154), I=1306/128=10
| 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |  10 < 128, encode(10), done
+---+---+---+---+---+---+---+---+
Copy the code

Decoding read the first byte, find the last five (11111) corresponding to the value I is 31(>= 2^ n-1), indicating that there are followed bytes; Let M=0, continue to read the next byte B, I = I + (B & 127) * 2^M = 31 + 26 * 1 = 57, M= M + 7 = 7, the most significant bit is 1, indicating that the byte sequence is not finished, B points to the next byte; I = I + (b&127) * 2^M = 57 + 10 * 128 = 1337, the most significant bit is 0, indicating the end of the bytecode, and I is returned

You can also do this here: 1306 = 0x51A = (0101 0001 1010)B, grouping the sequence of bits from the lowest to the highest by 7 groups, then there is the first group 001 1010, the second group 000 1010, plus the most significant bit 0/1 corresponds to the preceding byte

A character encoding

A string might represent the field or key of a Header entry. Character encodings are represented using byte sequences, either directly using the octet bytecode of the character or using Huffman encoding.

+---+---+---+---+---+---+---+---+
| H |    String Length (7+)     |
+---+---------------------------+
|  String Data (Length octets)  |
+-------------------------------+
Copy the code
  • H: ONE bit indicates whether Huffman encoding is used
  • String Length: indicates the Length of the byte sequence, that is, the Length of String Data. It is expressed in the integer encoding format of N=7
  • String Data: Octet sequence representation of a String. If H is 0, this is the octet representation of the original character. If H is 1, this is the Huffman encoding of the original character

RFC 7541 provides a Huffman encoding table for characters: Huffman Code, which is a Huffman encoding generated based on a large amount of HTTP header data.

  • The first column (sym) represents the character to be encoded, and the last special character “EOS” represents the end of the string
  • The second column (code as bits) is binary Huffman encoding, aligned towards the most significant bits
  • The third column (code as hex) is a hexadecimal Huffman code aligned to the least significant bit
  • The last column (len) represents the encoding length, in bits

If you use Huffman encoding, you might have an encoding that is not an integer byte, and fill it with 1 to make it an integer byte

Take the following example:

:authority: blog.wangriyu.wang

41 8e 8e 83 cc bf 81 d5    35 86 f5 6a fe 07 54 df
Copy the code

Literal Header Field with Incremental Indexing – Indexed Name is encoded in the following format

41 (0100 0001) indicates that the field has the index value 1, that is, authority, the first entry in the static table

8E (1000 1110) with a most significant bit of 1 means that the key is Huffman encoded, and 000 1110 means that the byte sequence length is 14

8E 83 cc bf 81 D5 35 86 F5 6a FE 07 54 DF

By Huffman encoding table shows 100011 – > ‘b’, 101000 – > ‘l’, 00111 – > ‘o’, 100110 – > ‘g’, 010111 – > ‘. ‘, 1111000 – > ‘w’, 00011 – > ‘a’, 101010 -> ‘n’, 100110 -> ‘g’, 101100 -> ‘r’, 00110 -> ‘i’, 1111010 -> ‘y’, 101101 -> ‘u’

8 e 83 cc bf 81 35 86 d5 f5 6 a fe 07 54 df | ⍒ 1000 1110 1000 0011 1100 1100 1011 1111 1000 0001 1101 0101 0011 0101 1000 0110 1111 0101 0110 1010 1111 1110 0000 0111 0101 0100 1101 1111 | ⍒ 100011 101000 00111 100110 010111 1111000 00011 101010 100110 101100 00110 1111010 101101 010111 1111000 00011 101010 100110 11111 | ⍒ blog. Wangriyu. Wang, the last 11111 used to fillCopy the code

Binary coding

Now comes the real codec specification for HPACK

Indexed Header Field Representation
  • Indexed Header Field

Indexes that can match in the index space are replaced with this form. The following indexes use the integer encoding above and N = 7. For example, method: GET can be represented by 0x82 (10000010)

+---+---+---+---+---+---+---+---+
| 1 |        Index (7+)         |
+---+---------------------------+
Copy the code

Literal Header Field Representation

A header that is not indexed has three representations, the first is indexed, the second is not indexed for the current hop, and the third is never allowed to be indexed

  1. Literal Header Field with Incremental Indexing

Starting with 01, this Header is added to the decoded Header List and inserted as a new entry into the dynamic index table

  • Literal Header Field with Incremental Indexing — Indexed Name

If the field is already indexed, but the key value is not indexed, such as header :authority: Wangriyu. wang: select * from blog. Wangriyu. wang: select * from blog. Wangriyu. wang: select * from blog.

+---+---+---+---+---+---+---+---+
| 0 | 1 |      Index (6+)       |
+---+---+-----------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
| Value String (Length octets)  |
+-------------------------------+
Copy the code

  • Literal Header Field with Incremental Indexing — New Name

If the field and key values are not indexed, such as upgrade-insecure-requests: 1, they are replaced with the following

+---+---+---+---+---+---+---+---+
| 0 | 1 |           0           |
+---+---+-----------------------+
| H |     Name Length (7+)      |
+---+---------------------------+
|  Name String (Length octets)  |
+---+---------------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
| Value String (Length octets)  |
+-------------------------------+
Copy the code

  1. Literal Header Field without Indexing

Starting with 0000, this header is added to the list of decoded headers, but not inserted into the dynamic index table

  • Literal Header Field without Indexing -- Indexed Name

If the field already has an index, but the key value is not indexed, it is replaced with the following form (index is encoded as an integer of N=4)

+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 |  Index (4+)   |
+---+---+-----------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
| Value String (Length octets)  |
+-------------------------------+
Copy the code

  • Literal Header Field without Indexing — New Name

If neither the field nor the key value is indexed, it is replaced with the following form. Such as strict – transport ws-security: Max – age = 63072000; includeSubdomains

+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 |       0       |
+---+---+-----------------------+
| H |     Name Length (7+)      |
+---+---------------------------+
|  Name String (Length octets)  |
+---+---------------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
| Value String (Length octets)  |
+-------------------------------+
Copy the code

  1. Never Indexed headers (Literal Header Field Never Indexed)

This is similar to the previous header except that the header is identified as 0001 and is added to the decoded header list but not inserted into the dynamic update table.

The difference is that this kind of header is what format to express, receive is also the same format, acting on each hop (hop), if the middle through the proxy, proxy must be unmodified forwarding can not be encoded.

The previous header only acts on the current hop and may be recoded after passing the proxy

Golang uses a Sensitive implementation to indicate which fields are definitely not indexed: golang.org/x/net/http2…

The RFC documentation details the reason for this: Never-Indexed Literals

The representation is the same as the header except for the identifier:

  • Literal Header Field Never Indexed -- Indexed Name
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 |  Index (4+)   |
+---+---+-----------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
| Value String (Length octets)  |
+-------------------------------+
Copy the code
  • Literal Header Field Never Indexed — New Name
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 |       0       |
+---+---+-----------------------+
| H |     Name Length (7+)      |
+---+---------------------------+
|  Name String (Length octets)  |
+---+---------------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
| Value String (Length octets)  |
+-------------------------------+
Copy the code
Dynamic Table Size Update

Start with 001. The function has been mentioned before

+---+---+---+---+---+---+---+---+
| 0 | 0 | 1 |   Max size (5+)   |
+---+---------------------------+
Copy the code

You can send an update with Max Size 0 to clear the dynamic index table

The instance

RFC 7541 RFC 7541 RFC 7541

What then ?

HTTP / 2 demo

http2.akamai.com/demo

http2.golang.org/

A before and after comparison of the site with h2 enabled, using WebPageTest, the first page is H1, the second page is H2:

Use HTTP/2 recommendations

Nginx simply adds HTTP2 to the corresponding HTTPS Settings to enable HTTP2

listen[: :] :443 ssl http2 ipv6only=on;
listen 443 ssl http2;
Copy the code

The following points apply equally to HTTP/1 and HTTP/2

1. Enable compression

Configuring gZIP and so on can make the transfer content smaller and faster

For example, nginx can add the following fields to the HTTP module, and other fields and detailed explanations can be Google

gzip on; // enable gzip_min_length 1k; gzip_comp_level 1; // Compression level gzip_types text/plain Application /javascript application/ X-javascript application/octet-stream application/json text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml; // The type of file to compress gzip_vary on; gzip_disable "MSIE [1-6]\.";Copy the code

2. Use caching

It is quite necessary to have a cache period for static resources, see another blog post on caching HTTP Message

For example, nginx adds the following fields to the server module to set the cache time

 location ~* ^.+\.(ico|gif|jpg|jpeg|png|moc|mtn|mp3|mp4|mov)$ {
   access_log   off;
   expires      30d;
 }

 location ~* ^.+\.(css|js|txt|xml|swf|wav|json)$ {
   access_log   off;
   expires      5d;
 }

 location ~* ^.+\.(html|htm)$ {
   expires      24h;
 }

 location ~* ^.+\.(eot|ttf|otf|woff|svg)$ {
   access_log   off;
   expires 30d;
 }
Copy the code

3. CDN acceleration

The benefits of CDN are proximity, low latency and fast access

Reduce DNS queries

Each domain name requires A DNS query, which can take anywhere from a few milliseconds to a few hundred milliseconds, or even slower in a mobile environment. The request will be blocked until DNS resolution is complete. Reducing DNS queries is also one of the optimizations

Browser DNS Prefetching technology is also an optimization method

5. Reduce redirection

Redirects can introduce new DNS queries, new TCP connections, and new HTTP requests, so reducing redirects is also important.

Browsers generally cache jumps specified by 301 Moved Permanently, so consider using status code 301 for permanent jumps. For HTTPS enabled web sites, configuring HSTS policies can also reduce HTTP to HTTPS redirects

However, the following are not recommended for HTTP/2

1. Domain name fragmentation

HTTP/2 using only one TCP connection for the same domain name is sufficient. Too many TCP connections waste resources and are not always effective

Furthermore, resource domainization breaks the priority nature of HTTP/2 and reduces header compression

2. Resource consolidation

Merging resources is not good for caching, and large single files are not good for HTTP/2 transmission, so fine-grained is better for HTTP/2 transmission

3. Resource inlining

HTTP/2 supports server-push, which is better than inlining

And inline resources cannot be cached efficiently

Multiple page inlining can also be wasteful if shared

HTTP/2 best practices

Use HTTP/2 with as few connections as possible, because the more requests and responses on the same connection, the more complete the dynamic dictionary, the better the compression of the header, and the more efficient multiplexing does not waste resources as with multiple connections

The following two points need to be noted:

  • Resources under the same domain name use the same connection, which is a feature of HTTP/2
  • HTTP/2 can merge multiple connections if resources under different domains resolve to the same IP address or use the same certificate (such as a generic domain certificate)

Therefore, using the same IP and certificate to deploy the Web service is currently the best choice, because it allows HTTP/ 2-enabled terminals to reuse the same connection and realize the benefits of HTTP/2 protocol; Terminals that only support HTTP/1.1 will establish different connections for different domain names to achieve more concurrent requests at the same time

For example, Google uses the same certificate for a series of websites:

However, this seems to cause a problem, I use nginx to build webServer, three virtual hosts, they share the same set of certificates, two of which I explicitly configure http2, and the other one I do not configure http2. As a result, I get http2 when I visit a site that is not configured with HTTP2.

Large picture transmission encountered problems

First, compare the page loading time of H1 and H2. In the figure, green represents the time of initiating the request and receiving the response and waiting for the load, while blue represents the time of downloading the load:

It can be found that the loading time of H2 is also a little slower than that of H1, especially when it comes to large images

This article tested h1 and H2 loading images in different scenarios: Real — World HTTP/2: 400GB of Images per day

The result:

  • For a typical graphic-rich, latency-bound interface. Visual completion is, on average, 5% faster using a high-speed, low-latency connection.

  • For an extremely image-heavy, bandwidth-bound page. Visual completion is on average 5-10% slower with the same connection, but the overall page load time is actually reduced because of less connection latency.

  • A high latency, low speed connection (e.g., slow 3G on mobile) causes a significant delay in the visual completion of the page, but H2 is significantly better.

In all of the tests, you can see that h2 made the overall page load faster and did better in the initial render, although the visual finish was slightly lower in the second case, but the overall effect was still good

The reason for the decrease in visual completion is that there is no limit on the number of simultaneous HTTP/1.x connections, h2 can initiate multiple image requests at the same time, and the server can respond to the load of images at the same time, as can be seen from the following GIF

Once the images are downloaded, the browser will draw them. However, small images will render faster after downloading, but if a large image happens to be the original view, it will take longer to load, delaying visual completion.

chrome bug

The GIF above is a test result in Safari, the images are downloaded successfully, while I am testing in Chrome and the following part of the images directly hang with ERR_SPDY_PROTOCOL_ERROR, and it is 100% replay

ERR_SPDY_PROTOCOL_ERROR: Server reset stream

Then I studied the frame sequence of HTTP/2. All the requests were successfully responded to in message no. 629, but only the data frames on stream 15 were returned, and more pictures were actually received than those corresponding to stream 15. Why?

Later, I continued to test and found that several large images were continuously requested. Although the HEADERS frames were opened on different streams and the HEADERS frames returned were still corresponding to the previous stream ID, the DATA frames of the response were all returned from the first stream opened.

In the case of small images, the stream is closed after a request response, and the next small image is returned on its own corresponding stream. Only a few large images in a row can do this, which is a strange mechanism that I haven’t found documentation to explain yet.

As for chrome, if you look at TCP packets, you can see that all the data is being sent on one connection, and then TCP packets are going to have all sorts of problems, like packet loss, retransmission, disordering, repacket, etc. It’s not clear if Safari is doing the same thing. Wireshark can unpack only Chrome packages, but not Safari packages

The Authoritative Guide to Web Performance mentions a possible problem with a TCP in HTTP/2: queue head blocking is eliminated, but still exists at the TCP level; If TCP window scaling is disabled, the bandwidth delay product effect may limit the throughput of the connection; The TCP congestion window shrinks when packets are lost.

On the one hand, TCP is the reason, and on the other hand, it should be a browser policy problem, probably also a Chrome bug. Comparing the two giFs, you will find that Safari receives load in turn, several of us receive a little bit and then several people receive it until all are accepted. Chrome, on the other hand, receives the images sequentially, and the images that follow may hang for a long time without a response.

Use progressive images

Progressive JPG instead of regular JPG is better for visual completion and smaller files:

Type convert –version to see if ImageMagic has been installed. If not, brew install Imagemagick for Mac and yum install imagemagick for Centos can be used

Check if it is progressive JPEG. If None is printed, it is not progressive JPEG. If the output JPEG is progressive JPEG:

$ identify -verbose filename.jpg | grep Interlace
Copy the code

To convert basic JPEG to Progressive JPEG, interlace parameter:

$convert-strip-interlace Plane source.jpg destination. JPG // You can also specify quality-90 // batch processing $for i in ./*.jpg; do convert -strip -interlace Plane $i $i; done
Copy the code

PNG and GIF can also be converted, but I have tried convert-strip-interlace Plane source.png destination. PNG but the converted images tend to be larger, so this is not recommended. You can convert source.png destination.jpg

ImageMagic also has many powerful features

JPG destination. JPG $convert source. JPG destination. PNG $find-e. -iregex to compress images larger than 100KB in the current directory by 75%'.*\.(jpg|png|bmp)'-size + 100k-exec convert-strip +profile "*" -quality 75 {} {} \;Copy the code

Pngquant is recommended for PNG compression

In addition, Photoshop can also set gradual or interleaved images:

Progressive image: Select JPEG as image format => Select continuous

Interlaced image: Select PNG/GIF as the image format => Select Interlaced

SPDY with HTTP2

SPDY is a predecessor of HTTP2 and has most of the same features as HTTP2, including server-side push, multiplexing, and framing as the minimum unit of transport. However, there are some implementation differences between SPDY and HTTP2. For example, SPDY uses the DEFLATE algorithm for header compression, while HTTP2 uses the HPACK algorithm for compression.

QUIC agreement

Google’s QUIC(Quick UDP Internet Connections) protocol inherits the features of SPDY. QUIC is a UDP version of the TCP + TLS + HTTP/2 alternative implementation.

QUIC can create lower latency connections and, like HTTP/2, solves the problem of packet loss by blocking only partial shunting, making it easier to establish connections across different networks – exactly the problem MPTCP is trying to solve.

QUIC is currently only available on Google Chrome and its backend servers, and while there is a third-party library called Libquic, the code is still hard to reuse elsewhere. The protocol was also introduced in draft by the IETF Communications Working Group.

Caddy: Web Server developed based on Go language, has good support for HTTP/2 and HTTPS, also started to support QUIC protocol (experimental)

Recommended tools

  • Chrome plugin: HTTP/2 and SPDY Indicator

If you visit a site that has HTTP/2 enabled, the icon lights up and clicking takes you to Chrome’s built-in HTTP/2 monitoring tool

  • Command-line tool: nghttp2

C language HTTP/2, you can use it to debug HTTP/2 requests

Brew install nghttp2 can be installed directly, once installed, type NGHTTP -nv https://nghttp2.org to view h2 requests

  • In addition to nghTTp2, you can also test with H2I http2: github.com/golang/net/…

  • Wireshark can also be used to unpack HTTP/2 traffic, but you must set the symmetric negotiation key provided by the browser or the private key provided by the server

If you can’t unpack the sslKeylog. log file to see if there is any data, if there is no data, the browser is not opened properly, use the command line to open the browser, so that the browser can read the environment variables and write the key to SSLKeylog, and this method seems to support Google Chrome and Firefox. For Safari is invalid

If sslKeylog. log does not unpack the sslkeylog.log file, open the SSL option and select the file again

Try more than once

  • H2o: Optimized HTTP Server with better support for HTTP/2

References

  • HTTP/2: New opportunities and challenges
  • HTTP2 is here, let’s optimize!
  • JerryQu’s Blog
  • http2 explained
  • NGINX HTTP2 White Paper
  • HTTP/2 Push: The details
  • The Definitive Guide to Web Performance