RFC 9113
HTTP/2
HTTP/2 ๐ฆ
Okay bestie, this is RFC 9113 ๐. The IETF put it out in June 2022 as a Standards Track document, which means it's an official Internet standard, not just a suggestion ๐ . The editors are M. Thomson, Ed. from Mozilla and C. Benfield, Ed. from Apple Inc. โจ It's also ISSN 2070-1721, and it obsoletes RFCs 7540 and 8740, so it replaces both of those. We're doing this in 19 parts, so this is just the first chunk ๐ง.
Abstract ๐ธ
Okay so the abstract says this spec describes an optimized way to express the semantics of the Hypertext Transfer Protocol (HTTP), which they call HTTP version 2 (HTTP/2) ๐. The big wins are more efficient use of network resources and lower latency, and it gets there by compressing fields and letting multiple concurrent exchanges happen on the same connection ๐ฅ. Also, this document obsoletes RFCs 7540 and 8740 ๐ซถ.
Status of This Memo ๐
This is an Internet Standards Track document ๐.
It's a product of the Internet Engineering Task Force (IETF), so it represents the consensus of the IETF community. It went through public review and was approved for publication by the Internet Engineering Steering Group (IESG) ๐. If you want more on Internet Standards, check out Section 2 of RFC 7841 โจ.
For the current status of this document, any errata, and how to send feedback, look at https://www.rfc-editor.org/info/rfc9113 ๐.
Copyright Notice ๐
Copyright (c) 2022 IETF Trust and the persons identified as the document authors. All rights reserved ๐ .
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) as they stand on the date of publication. Please actually read these, because they spell out your rights and restrictions for this document ๐. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions, and they come without warranty as described in the Revised BSD License โญ.
Table of Contents ๐ฆ
1. Introduction
2. HTTP/2 Protocol Overview
2.1. Document Organization
2.2. Conventions and Terminology
3. Starting HTTP/2
3.1. HTTP/2 Version Identification
3.2. Starting HTTP/2 for "https" URIs
3.3. Starting HTTP/2 with Prior Knowledge
3.4. HTTP/2 Connection Preface
4. HTTP Frames
4.1. Frame Format
4.2. Frame Size
4.3. Field Section Compression and Decompression
4.3.1. Compression State
5. Streams and Multiplexing
5.1. Stream States
5.1.1. Stream Identifiers
5.1.2. Stream Concurrency
5.2. Flow Control
5.2.1. Flow-Control Principles
5.2.2. Appropriate Use of Flow Control
5.2.3. Flow-Control Performance
5.3. Prioritization
5.3.1. Background on Priority in RFC 7540
5.3.2. Priority Signaling in This Document
5.4. Error Handling
5.4.1. Connection Error Handling
5.4.2. Stream Error Handling
5.4.3. Connection Termination
5.5. Extending HTTP/2
6. Frame Definitions
6.1. DATA
6.2. HEADERS
6.3. PRIORITY
6.4. RST_STREAM
6.5. SETTINGS
6.5.1. SETTINGS Format
6.5.2. Defined Settings
6.5.3. Settings Synchronization
6.6. PUSH_PROMISE
6.7. PING
6.8. GOAWAY
6.9. WINDOW_UPDATE
6.9.1. The Flow-Control Window
6.9.2. Initial Flow-Control Window Size
6.9.3. Reducing the Stream Window Size
6.10. CONTINUATION
7. Error Codes
8. Expressing HTTP Semantics in HTTP/2
8.1. HTTP Message Framing
8.1.1. Malformed Messages
8.2. HTTP Fields
8.2.1. Field Validity
8.2.2. Connection-Specific Header Fields
8.2.3. Compressing the Cookie Header Field
8.3. HTTP Control Data
8.3.1. Request Pseudo-Header Fields
8.3.2. Response Pseudo-Header Fields
8.4. Server Push
8.4.1. Push Requests
8.4.2. Push Responses
8.5. The CONNECT Method
8.6. The Upgrade Header Field
8.7. Request Reliability
8.8. Examples
8.8.1. Simple Request
8.8.2. Simple Response
8.8.3. Complex Request
8.8.4. Response with Body
8.8.5. Informational Responses
9. HTTP/2 Connections
9.1. Connection Management
9.1.1. Connection Reuse
9.2. Use of TLS Features
9.2.1. TLS 1.2 Features
9.2.2. TLS 1.2 Cipher Suites
9.2.3. TLS 1.3 Features
10. Security Considerations
10.1. Server Authority
10.2. Cross-Protocol Attacks
10.3. Intermediary Encapsulation Attacks
10.4. Cacheability of Pushed Responses
10.5. Denial-of-Service Considerations
10.5.1. Limits on Field Block Size
10.5.2. CONNECT Issues
10.6. Use of Compression
10.7. Use of Padding
10.8. Privacy Considerations
10.9. Remote Timing Attacks
11. IANA Considerations
11.1. HTTP2-Settings Header Field Registration
11.2. The h2c Upgrade Token
12. References
12.1. Normative References
12.2. Informative References
Appendix A. Prohibited TLS 1.2 Cipher Suites
Appendix B. Changes from RFC 7540
Acknowledgments
Contributors
Authors' Addresses
1. Introduction โจ
The performance of apps using the Hypertext Transfer Protocol (HTTP, [HTTP]) is tied to how each version of HTTP uses the underlying transport, and to the conditions that transport runs under ๐ธ.
Making multiple concurrent requests can cut latency and speed up your app ๐. HTTP/1.0 only let one request be outstanding at a time on a given TCP [TCP] connection ๐. HTTP/1.1 [HTTP/1.1] added request pipelining, but that only partly fixed request concurrency and still has application-layer head-of-line blocking, so it's not the full fix ๐. That's why HTTP/1.0 and HTTP/1.1 clients open multiple connections to a server to make concurrent requests ๐ง.
HTTP fields are also often repetitive and verbose ๐ฅฒ. That creates unnecessary network traffic and fills up the initial TCP congestion window really fast. The result can be excessive latency when several requests go out on a brand-new TCP connection ๐ญ.
HTTP/2 fixes this by defining an optimized mapping of HTTP's semantics onto an underlying connection ๐. It lets messages interleave on the same connection and uses an efficient coding for HTTP fields ๐ . It also lets you prioritize requests, so the more important ones finish sooner, which boosts performance even more ๐ฅ.
The protocol is also friendlier to the network, because fewer TCP connections are needed compared to HTTP/1.x ๐ฆ. That means less competition with other flows and longer-lived connections, which in turn lead to better use of the available network capacity ๐. Heads up though: this protocol does not address TCP head-of-line blocking ๐.
Last thing: HTTP/2 also makes processing messages more efficient thanks to binary message framing ๐.
This document obsoletes RFCs 7540 and 8740. Appendix B lists the notable changes ๐.
2. HTTP/2 Protocol Overview ๐
HTTP/2 gives HTTP semantics an optimized transport ๐ . It supports all of the core HTTP features but aims to be more efficient than HTTP/1.1 โจ.
HTTP/2 is a connection-oriented application-layer protocol that runs over a TCP connection ([TCP]) ๐ฆ. The client is the one that initiates the TCP connection ๐ซถ.
The basic protocol unit in HTTP/2 is a frame (Section 4.1) ๐ง. Each frame type has its own job. For example, HEADERS and DATA frames are the building blocks of HTTP requests and responses (Section 8.1) ๐. Other frame types, like SETTINGS, WINDOW_UPDATE, and PUSH_PROMISE, support the other HTTP/2 features ๐.
Multiplexing happens because each HTTP request/response exchange gets its own stream (Section 5) ๐. Streams are mostly independent of each other, so a blocked or stalled request or response doesn't stop progress on the other streams ๐ฅ.
Multiplexing works well only with flow control and prioritization ๐ . Flow control (Section 5.2) makes sure you can use multiplexed streams efficiently by limiting how much data gets sent to what the receiver can actually handle ๐. Prioritization (Section 5.3) makes sure limited resources get used the most effectively โญ. This revision of HTTP/2 deprecates the priority signaling scheme from [RFC7540] ๐.
HTTP fields on a connection can contain tons of redundant data, so the frames that carry them get compressed (Section 4.3) ๐๏ธ. This is especially helpful for request sizes in the common case, since many requests can fit into one packet ๐ฆ.
Finally, HTTP/2 adds a new, optional interaction mode where a server can push responses to a client (Section 8.4) ๐ธ. This lets a server speculatively send data it expects the client will need, trading some network usage for a possible latency gain ๐ง. To do it, the server synthesizes a request and sends it as a PUSH_PROMISE frame. Then it can send a response to that synthetic request on a separate stream ๐.
2.1. Document Organization ๐ง
The HTTP/2 specification is split into four parts:
- Starting HTTP/2 (Section 3) covers how an HTTP/2 connection gets started ๐.
- The frame (Section 4) and stream (Section 5) layers describe how HTTP/2 frames are structured and formed into multiplexed streams ๐.
- Frame (Section 6) and error (Section 7) definitions include the details of the frame and error types used in HTTP/2 ๐ .
- HTTP mappings (Section 8) and additional requirements (Section 9) describe how HTTP semantics get expressed using frames and streams โจ.
Some of the frame- and stream-layer concepts are isolated from HTTP, but this specification does not define a completely generic frame layer. The frame and stream layers are tailored to the needs of HTTP ๐ฆ.
2.2. Conventions and Terminology โจ
okay so bestie, first things first ๐ the BCP 14 key words ("MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL") mean what [RFC2119] and [RFC8174] say they mean, but only when they show up in ALL CAPS like this. lowercase "must" is just vibes, not a rule ๐
All numeric values are in network byte order ๐ข and values are unsigned unless it says otherwise. Literal values get written in decimal or hexadecimal, whichever fits, and hex always gets the 0x prefix so nobody confuses it with a decimal number. it's giving clarity ๐ซถ
This spec describes its binary formats using the conventions from Section 1.3 of RFC 9000 [QUIC] ๐ฆ heads up that this format is network byte order, and the high-valued bits are listed before the low-valued bits. no cap, read it in that order or you'll be lost ๐ญ
Here are the terms we'll be using:
- client: the endpoint that starts an HTTP/2 connection. Clients send HTTP requests and receive HTTP responses ๐
- connection: a transport-layer connection between two endpoints ๐
- connection error: an error that affects the entire HTTP/2 connection. big yikes ๐
- endpoint: either the client or the server of the connection ๐
- frame: the smallest unit of communication inside an HTTP/2 connection. It has a header plus a variable-length sequence of octets, structured according to the frame type ๐ง
- peer: an endpoint. When we're talking about one particular endpoint, "peer" means the endpoint on the other side, the one that's remote to the main subject ๐
- receiver: an endpoint that is receiving frames ๐
- sender: an endpoint that is transmitting frames ๐ฅ
- server: the endpoint that accepts an HTTP/2 connection. Servers receive HTTP requests and send HTTP responses โญ
- stream: a bidirectional flow of frames within the HTTP/2 connection ๐ฆ
- stream error: an error on an individual HTTP/2 stream. way less dramatic than a connection error, lowkey ๐ธ
Finally, the terms "gateway", "intermediary", "proxy", and "tunnel" are defined in Section 3.7 of [HTTP]. intermediaries literally play both client and server at different times, it's so iconic ๐ฏ And the term "content" (as it applies to message bodies) is defined in Section 6.4 of [HTTP] ๐
3. Starting HTTP/2 ๐
okay so the big question is: how does a client even find out whether a server speaks HTTP/2? ๐ต๏ธ Implementations that generate HTTP requests need to figure that out first ๐
HTTP/2 uses the "http" and "https" URI schemes from Section 4.2 of [HTTP], with the same default port numbers as HTTP/1.1 [HTTP/1.1]. Those URIs don't say anything about which HTTP versions an upstream server supports. "upstream server" just means the immediate peer the client wants to connect to, so lowkey it's a guessing game ๐
Figuring out HTTP/2 support is different for "http" and "https" URIs ๐ Discovery for "https" is covered in Section 3.2. For "http" URIs, HTTP/2 support can only be found out through out-of-band means, and you need prior knowledge of it, as described in Section 3.3 ๐ซถ
3.1. HTTP/2 Version Identification ๐ท๏ธ
This protocol has two identifiers ๐ Creating a connection based on either one means you're using the transport, framing, and message semantics described in this document. periodt ๐
- The string "h2" identifies the protocol where HTTP/2 uses Transport Layer Security (TLS); see Section 9.2. This is the identifier used in the TLS Application-Layer Protocol Negotiation (ALPN) extension [TLS-ALPN] field, and anywhere else HTTP/2 over TLS gets identified. The "h2" string is serialized into an ALPN protocol identifier as the two-octet sequence
0x68, 0x32๐ฆ - The "h2c" string used to be a token for the Upgrade header field in the HTTP Upgrade mechanism (Section 7.8 of [HTTP]). Nobody really used it widely, so this document deprecates it. The same goes for the HTTP2-Settings header field, which was used for the upgrade to "h2c" ๐ฅ not me deprecating a whole vibe ๐ญ
3.2. Starting HTTP/2 for "https" URIs ๐
A client that makes a request to an "https" URI uses TLS [TLS13] with the ALPN extension [TLS-ALPN] โจ
HTTP/2 over TLS uses the "h2" protocol identifier. The "h2c" identifier MUST NOT be sent by a client or selected by a server, because "h2c" describes a protocol that doesn't use TLS at all. that's a hard no ๐ โโ๏ธ
Once TLS negotiation is done, both the client and the server MUST send a connection preface (Section 3.4) ๐
3.3. Starting HTTP/2 with Prior Knowledge ๐ง
A client can learn that a particular server supports HTTP/2 by other means. For example, a client could be configured with knowledge that a server supports HTTP/2 ๐
A client that knows a server supports HTTP/2 can open a TCP connection, then send the connection preface (Section 3.4) followed by HTTP/2 frames. Servers can spot these connections by the presence of the connection preface ๐ This only affects setting up HTTP/2 connections over cleartext TCP. HTTP/2 connections over TLS MUST use protocol negotiation in TLS [TLS-ALPN], no exceptions ๐
Likewise, the server MUST send a connection preface (Section 3.4) ๐
Without any extra info, prior support for HTTP/2 is not a strong signal that a given server will support HTTP/2 on future connections. configs can change, configs can differ between instances in clustered servers, and network conditions can change too. it's a whole mood of uncertainty ๐ช๏ธ
3.4. HTTP/2 Connection Preface ๐
In HTTP/2, each endpoint is REQUIRED to send a connection preface. It's the final confirmation of the protocol in use, and it sets up the initial settings for the connection. The client and the server each send a different connection preface ๐
The client connection preface starts with a sequence of 24 octets, which in hex is:
0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
That is, the connection preface starts with the string "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n". This sequence MUST be followed by a SETTINGS frame (Section 6.5), which MAY be empty. The client sends the client connection preface as the first application data octets of a connection ๐
| Note: The client connection preface is selected so that a large
| proportion of HTTP/1.1 or HTTP/1.0 servers and intermediaries
| do not attempt to process further frames. Note that this does
| not address the concerns raised in [TALKING].
The server connection preface is a potentially empty SETTINGS frame (Section 6.5) that MUST be the first frame the server sends in the HTTP/2 connection ๐
The SETTINGS frames received from a peer as part of the connection preface MUST be acknowledged (see Section 6.5.3) after sending the connection preface ๐
To avoid unnecessary latency, clients are allowed to send additional frames to the server right after the client connection preface, without waiting for the server connection preface. buuut it's important to note that the server's SETTINGS frame might include settings that change how a client is supposed to talk to the server. When the client gets that SETTINGS frame, it's expected to honor whatever settings were established ๐ In some configurations, the server can send SETTINGS before the client sends more frames, which gives everyone a chance to dodge this issue โจ
Clients and servers MUST treat an invalid connection preface as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. A GOAWAY frame (Section 6.8) MAY be omitted in this case, since an invalid preface means the peer isn't using HTTP/2 at all ๐
4. HTTP Frames ๐๏ธ
Once the HTTP/2 connection is established, endpoints can start exchanging frames ๐ฆ
4.1. Frame Format ๐งฑ
All frames begin with a fixed 9-octet header, followed by a variable-length frame payload ๐
HTTP Frame {
Length (24),
Type (8),
Flags (8),
Reserved (1),
Stream Identifier (31),
Frame Payload (..),
}
Figure 1: Frame Layout
The fields of the frame header are defined as:
- Length: the length of the frame payload, expressed as an unsigned 24-bit integer in units of octets. Values greater than 2^14 (16,384) MUST NOT be sent unless the receiver has set a larger value for SETTINGS_MAX_FRAME_SIZE. The 9 octets of the frame header are not included in this value ๐ฐ
- Type: the 8-bit type of the frame. The frame type decides the format and semantics of the frame. Frames defined in this document are listed in Section 6. Implementations MUST ignore and discard frames of unknown types ๐๏ธ
- Flags: an 8-bit field reserved for boolean flags specific to the frame type. Flags get semantics based on their frame type. Unused flags are the ones with no defined semantics for a particular frame type. Unused flags MUST be ignored on receipt and MUST be left unset (0x00) when sending ๐ฉ
- Reserved: a reserved 1-bit field. The semantics of this bit are undefined, so the bit MUST remain unset (0x00) when sending and MUST be ignored when receiving ๐คซ
- Stream Identifier: a stream identifier (see Section 5.1.1) expressed as an unsigned 31-bit integer. The value 0x00 is reserved for frames associated with the connection as a whole, as opposed to an individual stream ๐
The structure and content of the frame payload depend entirely on the frame type ๐
4.2. Frame Size ๐
The size of a frame payload is limited by the maximum size a receiver advertises in the SETTINGS_MAX_FRAME_SIZE setting. This setting can have any value between 2^14 (16,384) and 2^24-1 (16,777,215) octets, inclusive ๐ค
All implementations MUST be capable of receiving and minimally processing frames up to 2^14 octets in length, plus the 9-octet frame header (Section 4.1). The size of the frame header is not included when describing frame sizes ๐ซถ
| Note: Certain frame types, such as PING (Section 6.7), impose
| additional limits on the amount of frame payload data allowed.
An endpoint MUST send an error code of FRAME_SIZE_ERROR if a frame exceeds the size defined in SETTINGS_MAX_FRAME_SIZE, exceeds any limit defined for the frame type, or is too small to contain mandatory frame data. A frame size error in a frame that could alter the state of the entire connection MUST be treated as a connection error (Section 5.4.1). This includes any frame carrying a field block (Section 4.3) (that is, HEADERS, PUSH_PROMISE, and CONTINUATION), a SETTINGS frame, and any frame with a stream identifier of 0. this is serious business, bestie, don't mess with frame sizes ๐ญ๐จ
Endpoints are not obligated to use all available space in a frame ๐ Responsiveness can get better by using frames smaller than the permitted maximum size. Sending large frames can delay time-sensitive frames (such as RST_STREAM, WINDOW_UPDATE, or PRIORITY), and if those get blocked behind a large frame, performance could suffer ๐ข
4.3. Field Section Compression and Decompression โจ
okay bestie, so field section compression is the process of squishing a set of field lines (Section 5.2 of [HTTP]) into a field block ๐. And field section decompression is the reverse, decoding a field block back into a set of field lines ๐ฆ. All the nitty-gritty details of how HTTP/2 does this live in [COMPRESSION], which, for historical reasons, calls these processes header compression and decompression ๐
Each field block carries all of the compressed field lines of a single field section ๐. Header sections also include control data for the message, in the form of pseudo-header fields (Section 8.3) that use the same format as a field line โจ.
Note: RFC 7540 [RFC7540] used the term "header block" instead of the more generic "field block" ๐
Field blocks carry control data and header sections for requests, responses, promised requests, and pushed responses (see Section 8.4) ๐ธ. All of these messages can optionally include a field block that carries a trailer section, except for interim responses and requests contained in PUSH_PROMISE (Section 6.6) frames ๐
A field section is a collection of field lines, and each field line in a field block carries a single value ๐ซถ. The serialized field block gets chopped up into one or more octet sequences called field block fragments ๐งฉ. The first field block fragment is sent inside the frame payload of HEADERS (Section 6.2) or PUSH_PROMISE (Section 6.6) frames, and either of those can be followed by CONTINUATION (Section 6.10) frames that carry the later fragments ๐
The Cookie header field [COOKIE] gets special treatment from the HTTP mapping (see Section 8.2.3) ๐
A receiving endpoint puts the field block back together by concatenating its fragments, and then decompresses the block to rebuild the field section ๐ฅ
A complete field section looks like one of these:
* a single HEADERS or PUSH_PROMISE frame, with the END_HEADERS flag set, or
* a HEADERS or PUSH_PROMISE frame with the END_HEADERS flag unset, followed by one or more CONTINUATION frames, where the last CONTINUATION frame has the END_HEADERS flag set ๐
Each field block gets processed as a discrete unit ๐. Field blocks MUST be sent as a contiguous sequence of frames, with no interleaved frames of any other type and no frames from any other stream ๐ โโ๏ธ The last frame in a sequence of HEADERS or CONTINUATION frames has the END_HEADERS flag set, and the last frame in a sequence of PUSH_PROMISE or CONTINUATION frames has the END_HEADERS flag set too ๐ This way, a field block acts like it's logically equivalent to a single frame โจ
Field block fragments can ONLY be sent as the frame payload of HEADERS, PUSH_PROMISE, or CONTINUATION frames, because those are the frames that carry data able to change the compression context a receiver keeps ๐ง An endpoint that receives HEADERS, PUSH_PROMISE, or CONTINUATION frames MUST reassemble field blocks and run decompression even if the frames are going to be thrown away ๐ญ A receiver MUST terminate the connection with a connection error (Section 5.4.1) of type COMPRESSION_ERROR if it fails to decompress a field block ๐
Also, a decoding error in a field block MUST be treated as a connection error (Section 5.4.1) of type COMPRESSION_ERROR ๐จ
4.3.1. Compression State โจ
Field compression is stateful, bestie ๐ง Each endpoint has an HPACK encoder context and an HPACK decoder context, and both are used for encoding and decoding every field block on a connection ๐ซง Section 4 of [COMPRESSION] defines the dynamic table, which is the main state for each context ๐
The dynamic table has a maximum size that is set by an HPACK decoder ๐ An endpoint tells its peer the size its HPACK decoder context chose by using the SETTINGS_HEADER_TABLE_SIZE setting (see Section 6.5.2) ๐ When a connection is established, the dynamic table size for the HPACK decoder and encoder at both endpoints starts at 4,096 bytes, which is the initial value of SETTINGS_HEADER_TABLE_SIZE ๐ธ
Any change to the maximum value set with SETTINGS_HEADER_TABLE_SIZE takes effect when the endpoint acknowledges settings (Section 6.5.3) โญ The HPACK encoder at that endpoint can set the dynamic table to any size up to the maximum value the decoder set ๐ An HPACK encoder declares the size of the dynamic table with a Dynamic Table Size Update instruction (Section 6.3 of [COMPRESSION]) ๐ฆ
Once an endpoint acknowledges a change to SETTINGS_HEADER_TABLE_SIZE that drops the maximum below the current size of the dynamic table, its HPACK encoder MUST start the next field block with a Dynamic Table Size Update instruction that sets the dynamic table to a size less than or equal to the reduced maximum (see Section 4.2 of [COMPRESSION]) ๐ค An endpoint MUST treat a field block that follows an acknowledgment of the reduction to the maximum dynamic table size as a connection error (Section 5.4.1) of type COMPRESSION_ERROR if it does not start with a conformant Dynamic Table Size Update instruction ๐ซ
Implementers are advised that reducing the value of SETTINGS_HEADER_TABLE_SIZE is not widely interoperable ๐ Using the connection preface to reduce the value below the initial value of 4,096 is somewhat better supported, but it might still fail with some implementations ๐
5. Streams and Multiplexing ๐ฆ
okay so a "stream" is an independent, bidirectional sequence of frames that the client and server swap inside an HTTP/2 connection ๐ Streams have a few major vibes:
- A single HTTP/2 connection can hold multiple concurrently open streams, with either endpoint interleaving frames from several streams ๐ซ
- Streams can be established and used unilaterally, or shared by either endpoint ๐ค
- Streams can be closed by either endpoint ๐ช
- The order frames are sent in is significant. Recipients process frames in the order they arrive, and the order of HEADERS and DATA frames is semantically significant ๐งฉ
- Streams are identified by an integer, and stream identifiers are assigned by the endpoint that starts the stream ๐ข
5.1. Stream States ๐
The lifecycle of a stream is shown in Figure 2 ๐
+--------+
send PP | | recv PP
,--------+ idle +--------.
/ | | \
v +--------+ v
+----------+ | +----------+
| | | send H / | |
,------+ reserved | | recv H | reserved +------.
| | (local) | | | (remote) | |
| +---+------+ v +------+---+ |
| | +--------+ | |
| | recv ES | | send ES | |
| send H | ,-------+ open +-------. | recv H |
| | / | | \ | |
| v v +---+----+ v v |
| +----------+ | +----------+ |
| | half- | | | half- | |
| | closed | | send R / | closed | |
| | (remote) | | recv R | (local) | |
| +----+-----+ | +-----+----+ |
| | | | |
| | send ES / | recv ES / | |
| | send R / v send R / | |
| | recv R +--------+ recv R | |
| send R / `----------->| |<-----------' send R / |
| recv R | closed | recv R |
`----------------------->| |<-----------------------'
+--------+
Figure 2: Stream States
send: endpoint sends this frame ๐ค recv: endpoint receives this frame ๐ฅ H: HEADERS frame (with implied CONTINUATION frames) ๐ ES: END_STREAM flag ๐ R: RST_STREAM frame ๐ฅ PP: PUSH_PROMISE frame (with implied CONTINUATION frames); state transitions are for the promised stream ๐
Heads up: this diagram only shows stream state transitions and the frames and flags that cause them ๐ CONTINUATION frames don't cause state transitions on their own, since they're basically part of the HEADERS or PUSH_PROMISE they follow ๐ซถ For state transition purposes, the END_STREAM flag is handled as a separate event from the frame that carries it, so a HEADERS frame with END_STREAM set can cause two state transitions ๐คฏ
Both endpoints have a subjective view of a stream's state, and those views can differ while frames are in transit ๐ซ๏ธ Endpoints don't coordinate stream creation, since streams get created unilaterally by either endpoint ๐ The negative consequences of a state mismatch are limited to the "closed" state after sending RST_STREAM, where frames might keep arriving for a little while after closing ๐ฌ
Streams have the following states:
idle: All streams start in the "idle" state. ๐ฑ
The following transitions are valid from this state:
- Sending a HEADERS frame as a client, or receiving a HEADERS frame as a server, causes the stream to become "open". The stream identifier is selected as described in Section 5.1.1. The same HEADERS frame can also cause a stream to immediately become "half-closed". ๐ช
- Sending a PUSH_PROMISE frame on another stream reserves the idle stream that is identified for later use. The stream state for the reserved stream transitions to "reserved (local)". Only a server may send PUSH_PROMISE frames. ๐
- Receiving a PUSH_PROMISE frame on another stream reserves an idle stream that is identified for later use. The stream state for the reserved stream transitions to "reserved (remote)". Only a client may receive PUSH_PROMISE frames. ๐
- Note that the PUSH_PROMISE frame is not sent on the idle stream but references the newly reserved stream in the Promised Stream ID field. ๐
- Opening a stream with a higher-valued stream identifier causes the stream to transition immediately to a "closed" state; note that this transition is not shown in the diagram. ๐
Receiving any frame other than HEADERS or PRIORITY on a stream in this state MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. If this stream is initiated by the server, as described in Section 5.1.1, then receiving a HEADERS frame MUST also be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. ๐จ
reserved (local): A stream in the "reserved (local)" state is one that has been promised by sending a PUSH_PROMISE frame. A PUSH_PROMISE frame reserves an idle stream by associating the stream with an open stream that was initiated by the remote peer (see Section 8.4). ๐
In this state, only the following transitions are possible:
- The endpoint can send a HEADERS frame. This causes the stream to open in a "half-closed (remote)" state. ๐จ
- Either endpoint can send a RST_STREAM frame to cause the stream to become "closed". This releases the stream reservation. ๐๏ธ
An endpoint MUST NOT send any type of frame other than HEADERS, RST_STREAM, or PRIORITY in this state. ๐ โโ๏ธ
A PRIORITY or WINDOW_UPDATE frame MAY be received in this state. Receiving any type of frame other than RST_STREAM, PRIORITY, or WINDOW_UPDATE on a stream in this state MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. ๐ซ
"reserved (remote)": okay so this is a stream that a remote peer has reserved for itself ๐ง It's giving "I'll save you a seat, bestie."
In this state, only these transitions are possible:
- Receiving a
HEADERSframe causes the stream to transition to "half-closed (local)". ๐ - Either endpoint can send a
RST_STREAMframe to cause the stream to become "closed". This releases the stream reservation. ๐ โโ๏ธ
An endpoint MUST NOT send any type of frame other than RST_STREAM, WINDOW_UPDATE, or PRIORITY in this state. ๐
Receiving any type of frame other than HEADERS, RST_STREAM, or PRIORITY on a stream in this state MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. Not me being dramatic, but this is a hard boundary ๐
"open": a stream in the "open" state may be used by both peers to send frames of any type. Literally the most social state ๐ In this state, sending peers observe advertised stream-level flow-control limits (Section 5.2).
From this state, either endpoint can send a frame with an END_STREAM flag set, which causes the stream to transition into one of the "half-closed" states. An endpoint sending an END_STREAM flag causes the stream state to become "half-closed (local)". An endpoint receiving an END_STREAM flag causes the stream state to become "half-closed (remote)". ๐ฆ
Either endpoint can send a RST_STREAM frame from this state, which causes it to transition immediately to "closed". Rip, instant exit ๐ชโจ
"half-closed (local)": a stream in this state cannot be used for sending frames other than WINDOW_UPDATE, PRIORITY, and RST_STREAM. So you're kinda on mute, but you can still wave ๐
A stream transitions from this state to "closed" when a frame is received with the END_STREAM flag set, or when either peer sends a RST_STREAM frame. ๐ธ
An endpoint can receive any type of frame in this state. Providing flow-control credit using WINDOW_UPDATE frames is necessary to continue receiving flow-controlled frames. In this state, a receiver can ignore WINDOW_UPDATE frames, which might arrive for a short period after a frame with the END_STREAM flag set is sent. The vibe is "I'm still listening, but I'm not replying to that." ๐ซถ
PRIORITY frames can be received in this state. ๐
"half-closed (remote)": a stream that is "half-closed (remote)" is no longer being used by the peer to send frames. In this state, an endpoint is no longer obligated to maintain a receiver flow-control window. Freedom, bestie ๐๏ธ
If an endpoint receives additional frames, other than WINDOW_UPDATE, PRIORITY, or RST_STREAM, for a stream that is in this state, it MUST respond with a stream error (Section 5.4.2) of type STREAM_CLOSED. No cap, this is the "you're not on the list anymore" moment ๐ซ๐
A stream that is "half-closed (remote)" can be used by the endpoint to send frames of any type. In this state, the endpoint continues to observe advertised stream-level flow-control limits (Section 5.2). ๐
A stream can transition from this state to "closed" by sending a frame with the END_STREAM flag set, or when either peer sends a RST_STREAM frame. โจ
"closed": the "closed" state is the terminal state. The end of the road, the final episode, the group chat has been left ๐ญ
A stream enters the "closed" state after an endpoint both sends and receives a frame with an END_STREAM flag set. A stream also enters the "closed" state after an endpoint either sends or receives a RST_STREAM frame. ๐
An endpoint MUST NOT send frames other than PRIORITY on a closed stream. An endpoint MAY treat receipt of any other type of frame on a closed stream as a connection error (Section 5.4.1) of type STREAM_CLOSED, except as noted below. Very strict, very iconic ๐
An endpoint that sends a frame with the END_STREAM flag set, or a RST_STREAM frame, might receive a WINDOW_UPDATE or RST_STREAM frame from its peer in the time before the peer receives and processes the frame that closes the stream. Ships pass in the night, ngl ๐ข
An endpoint that sends a RST_STREAM frame on a stream that is in the "open" or "half-closed (local)" state could receive any type of frame. The peer might have sent or enqueued for sending these frames before processing the RST_STREAM frame. An endpoint MUST minimally process and then discard any frames it receives in this state. This means updating header compression state for HEADERS and PUSH_PROMISE frames. Receiving a PUSH_PROMISE frame also causes the promised stream to become "reserved (remote)", even when the PUSH_PROMISE frame is received on a closed stream. Additionally, the content of DATA frames counts toward the connection flow-control window. Even the leftovers count, babe ๐
An endpoint can perform this minimal processing for all streams that are in the "closed" state. Endpoints MAY use other signals to detect that a peer has received the frames that caused the stream to enter the "closed" state, and treat receipt of any frame other than PRIORITY as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. Endpoints can use frames that indicate that the peer has received the closing signal to drive this. Endpoints SHOULD NOT use timers for this purpose. For example, an endpoint that sends a SETTINGS frame after closing a stream can safely treat receipt of a DATA frame on that stream as an error after receiving an acknowledgment of the settings. Other things that might be used are PING frames, receiving data on streams that were created after closing the stream, or responses to requests created after closing the stream. Vibe check instead of a stopwatch โฐ๐
โโ๏ธ
In the absence of more specific rules, implementations SHOULD treat the receipt of a frame that is not expressly permitted in the description of a state as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. Note that PRIORITY can be sent and received in any stream state. It's the plus-one that goes everywhere ๐งโจ
The rules in this section only apply to frames defined in this document. Receipt of frames for which the semantics are unknown cannot be treated as an error, as the conditions for sending and receiving those frames are also unknown; see Section 5.5. We don't judge what we don't know ๐ธ
An example of the state transitions for an HTTP request/response exchange can be found in Section 8.8. An example of the state transitions for server push can be found in Sections 8.4.1 and 8.4.2. Go look, it's a whole thing ๐
5.1.1. Stream Identifiers ๐ชช
Streams are identified by an unsigned 31-bit integer. Streams initiated by a client MUST use odd-numbered stream identifiers, and those initiated by the server MUST use even-numbered stream identifiers. A stream identifier of zero (0x00) is used for connection control messages. The stream identifier of zero cannot be used to establish a new stream. Zero is for the group chat settings, not for hanging out ๐
The identifier of a newly established stream MUST be numerically greater than all streams that the initiating endpoint has opened or reserved. This governs streams that are opened using a HEADERS frame and streams that are reserved using PUSH_PROMISE. An endpoint that receives an unexpected stream identifier MUST respond with a connection error (Section 5.4.1) of type PROTOCOL_ERROR. Numbers only go up, bestie, no backsies ๐
A HEADERS frame will transition the client-initiated stream identified by the stream identifier in the frame header from "idle" to "open". A PUSH_PROMISE frame will transition the server-initiated stream identified by the Promised Stream ID field in the frame payload from "idle" to "reserved (local)" or "reserved (remote)". When a stream transitions out of the "idle" state, all streams in the "idle" state that might have been opened by the peer with a lower-valued stream identifier immediately transition to "closed". That is, an endpoint may skip a stream identifier, with the effect being that the skipped stream is immediately closed. Ghosted, but make it efficient ๐ป
Stream identifiers cannot be reused. Long-lived connections can result in an endpoint exhausting the available range of stream identifiers. A client that is unable to establish a new stream identifier can establish a new connection for new streams. A server that is unable to establish a new stream identifier can send a GOAWAY frame so that the client is forced to open a new connection for new streams. IDs are a one-time thing, so when they run out it's a fresh connection era ๐๐
5.1.2. Stream Concurrency ๐ฆ
A peer can limit the number of concurrently active streams using the SETTINGS_MAX_CONCURRENT_STREAMS parameter (see Section 6.5.2) within a SETTINGS frame. The maximum concurrent streams setting is specific to each endpoint and applies only to the peer that receives the setting. That is, clients specify the maximum number of concurrent streams the server can initiate, and servers specify the maximum number of concurrent streams the client can initiate. Setting boundaries, but make it cute ๐
Streams that are in the "open" state, or in either of the "half-closed" states, count toward the maximum number of streams that an endpoint is permitted to open. Streams in any of these three states count toward the limit advertised in the SETTINGS_MAX_CONCURRENT_STREAMS setting. Streams in either of the "reserved" states do not count toward the stream limit. The reserved ones get a VIP pass ๐๏ธ
Endpoints MUST NOT exceed the limit set by their peer. An endpoint that receives a HEADERS frame that causes its advertised concurrent stream limit to be exceeded MUST treat this as a stream error (Section 5.4.2) of type PROTOCOL_ERROR or REFUSED_STREAM. The choice of error code determines whether the endpoint wishes to enable automatic retry (see Section 8.7 for details). Don't overbook the party, babe ๐ซ๐
An endpoint that wishes to reduce the value of SETTINGS_MAX_CONCURRENT_STREAMS to a value that is below the current number of open streams can either close streams that exceed the new value or allow streams to complete. Downsizing the guest list with grace ๐ซง
5.2. Flow Control ๐
Using streams for multiplexing introduces contention over use of the TCP connection, resulting in blocked streams. A flow-control scheme ensures that streams on the same connection do not destructively interfere with each other. Flow control is used for both individual streams and the connection as a whole. Everyone gets their turn, it's giving fairness ๐
HTTP/2 provides for flow control through use of the WINDOW_UPDATE frame (Section 6.9). ๐ฆ
5.2.1. Flow-Control Principles ๐
okay so the whole point of HTTP/2 stream flow control is to let people build a bunch of different flow-control algorithms without the protocol having to change every time ๐ฆ here's how it works:
- Flow control is per connection. it only runs between the two endpoints of one hop, not across the whole end-to-end path ๐ค๏ธ
- Flow control runs on WINDOW_UPDATE frames. receivers tell the sender how many octets they're ready to take, both for each stream and for the whole connection. it's a credit system, basically ๐ณ
- Flow control goes one direction at a time, and the receiver is the one really in charge. a receiver MAY pick whatever window size it wants for each stream and for the connection. a sender MUST respect the flow-control limits a receiver sets. clients, servers, and intermediaries each advertise their own window as receivers, and each one follows the limits its peer sets when it's sending. everyone does their own thing, independently ๐
- The starting window is 65,535 octets for new streams and for the overall connection. that's the default, bestie ๐ธ
- Only the frame type decides whether flow control applies. out of the frames in this document, only DATA frames are subject to flow control. every other frame type skips the advertised window entirely, so the important control frames never get stuck behind flow control ๐ซถ
- An endpoint can turn off its own flow control if it wants to. what it can't do is ignore the flow-control signals coming from its peer ๐ โโ๏ธ
- HTTP/2 only defines the format and meaning of the WINDOW_UPDATE frame (Section 6.9). this document doesn't say how a receiver decides when to send that frame or what value to put in it, and it doesn't say how a sender picks when to send packets either. implementations can choose whatever algorithm fits their needs โจ
Implementations also have their own jobs to handle: prioritizing when requests and responses get sent, figuring out how to avoid head-of-line blocking for requests, and managing when new streams get created. the choices they make for those can interact with whatever flow-control algorithm they're using ๐ง
5.2.2. Appropriate Use of Flow Control ๐
Flow control exists to protect endpoints that are running under resource constraints ๐ก๏ธ picture a proxy that has to share memory across tons of connections, and maybe it has a slow upstream connection but a fast downstream one. flow control is for the case where the receiver can't process data on one stream right now but still wants to keep handling the other streams on the same connection.
Deployments that don't need that ability can advertise a flow-control window of the maximum size, which is 2^31-1, and keep that window topped up by sending a WINDOW_UPDATE frame whenever any data arrives. this basically turns flow control off for that receiver ๐ the flip side is that a sender is always bound by whatever flow-control window the receiver advertised. no exceptions, bestie.
Deployments with tight resources, like limited memory, can use flow control to cap how much memory a peer is allowed to eat up ๐ but heads up: if flow control is turned on without knowing the bandwidth * delay product (see [RFC7323]), it can lead to suboptimal use of the network resources that are available. so be careful!! ๐ฌ
Even when you know the current bandwidth * delay product, flow control can still be super tricky to implement ๐ญ Endpoints MUST read and process HTTP/2 frames from the TCP receive buffer as soon as data is available. if they don't read promptly, they could end up in a deadlock, because critical frames like WINDOW_UPDATE never get read and acted on. and reading frames promptly doesn't open an endpoint up to resource exhaustion attacks, since HTTP/2 flow control limits how many resources get committed ๐ช
5.2.3. Flow-Control Performance ๐
If an endpoint can't guarantee that its peer always has flow-control window space bigger than the peer's bandwidth * delay product on this connection, then its receive throughput is going to be capped by HTTP/2 flow control. and that means degraded performance, which is not the vibe ๐
Sending WINDOW_UPDATE frames on time can really help performance ๐ endpoints will want to balance getting better receive throughput against managing the risk of resource exhaustion. and when they're deciding how to manage window sizes, they should take careful note of Section 10.5 ๐ง
5.3. Prioritization ๐
In a multiplexed protocol like HTTP/2, deciding how bandwidth and computation get shared out among streams can make or break performance ๐ฅ a bad prioritization scheme can make HTTP/2 perform poorly. and with no parallelism at the TCP layer, performance could end up being way worse than HTTP/1.1. yikes ๐ญ
A good prioritization scheme gets a lot of mileage out of contextual knowledge: what the resources actually contain, how they relate to each other, and how a peer is going to use them ๐ง clients can know things about how important their requests are, and that info can matter a lot to the server. so having clients provide priority information can improve performance in those cases โจ
5.3.1. Background on Priority in RFC 7540 ๐
RFC 7540 came up with a rich system for signaling the priority of requests. but it turned out to be complicated, and it wasn't implemented the same way everywhere ๐
The scheme was so flexible that clients could express priorities in really different ways, and the approaches they picked didn't really line up. for servers, supporting the scheme generically was a lot of work. priority support was uneven across both clients and servers, and lots of server deployments just ignored the client's signals when deciding how to handle requests ๐
So, in short, the prioritization signaling in RFC 7540 [RFC7540] didn't really succeed ๐
5.3.2. Priority Signaling in This Document ๐ฆ
This update to HTTP/2 deprecates the priority signaling that RFC 7540 [RFC7540] defined. most of the text about priority signals isn't included in this document anymore. the frame field descriptions and some of the mandatory handling stick around, though, so implementations of this document can still interoperate with implementations that use the RFC 7540 priority signaling ๐ซถ
A full description of the RFC 7540 priority scheme is still in Section 5.3 of [RFC7540] if you want to go deep.
Priority signaling is necessary for good performance in lots of cases. where it really matters, endpoints are encouraged to use a different scheme instead, like the one described in [HTTP-PRIORITY] ๐
Even though RFC 7540's priority signaling wasn't widely adopted, the info it gives can still be useful when there's nothing better to go on. endpoints that get priority signals in HEADERS or PRIORITY frames can benefit from using that info. implementations that consume these signals wouldn't gain anything by throwing them away when no alternatives exist, so keep them ๐
Servers SHOULD use other contextual information to decide the priority of requests when there aren't any priority signals. servers MAY read the complete absence of signals as a sign that the client hasn't implemented the feature. the defaults described in Section 5.3.5 of [RFC7540] are known to perform poorly under most conditions, and nobody is likely to be using them on purpose ๐
5.4. Error Handling โ ๏ธ
HTTP/2 framing allows two classes of errors:
- An error condition that makes the entire connection unusable is a connection error.
- An error in an individual stream is a stream error.
A list of error codes is in Section 7 ๐
An endpoint might run into frames that would cause more than one error. Implementations MAY discover multiple errors while processing, but they SHOULD report at most one stream error and one connection error as a result. the first stream error reported for a given stream stops any other errors on that stream from being reported ๐ the protocol does allow multiple GOAWAY frames, but an endpoint SHOULD report just one type of connection error, unless an error shows up during a graceful shutdown. if that happens, an endpoint MAY send an additional GOAWAY frame carrying the new error code, on top of any earlier GOAWAY that had NO_ERROR in it ๐
If an endpoint detects several different errors, it MAY choose to report any one of them. if a frame causes a connection error, that error MUST be reported. on top of that, an endpoint MAY use any error code that applies when it detects an error condition. a generic error code, like PROTOCOL_ERROR or INTERNAL_ERROR, can always be used in place of a more specific one ๐งธ
5.4.1. Connection Error Handling ๐
A connection error is any error that stops the frame layer from processing further or corrupts some connection state ๐ฅ
An endpoint that runs into a connection error SHOULD first send a GOAWAY frame (Section 6.8) with the stream identifier of the last stream it successfully received from its peer. the GOAWAY frame includes an error code (Section 7) that says why the connection is ending. after sending the GOAWAY frame for an error condition, the endpoint MUST close the TCP connection ๐
The GOAWAY might not get reliably received by the other endpoint. in the case of a connection error, GOAWAY is only a best-effort attempt to tell the peer why the connection is being terminated ๐ค
An endpoint can end a connection at any time. in particular, an endpoint MAY choose to treat a stream error as a connection error. Endpoints SHOULD send a GOAWAY frame when ending a connection, as long as the circumstances allow for it โจ
5.4.2. Stream Error Handling ๐ฏ
A stream error is an error tied to one specific stream, and it doesn't affect how other streams get processed ๐ท
An endpoint that detects a stream error sends a RST_STREAM frame (Section 6.4). that frame carries the stream identifier of the stream where the error happened, and it includes an error code that says what type of error it was.
A RST_STREAM is the last frame an endpoint can send on a stream. the peer that sends the RST_STREAM frame MUST be ready to receive any frames that were sent or queued up for sending by the remote peer. those frames can be ignored, except where they change connection state, like the state kept for field section compression (Section 4.3) or flow control ๐งฉ
Normally, an endpoint SHOULD NOT send more than one RST_STREAM frame for any stream. but an endpoint MAY send additional RST_STREAM frames if it gets frames on a closed stream after more than a round-trip time. that behavior is allowed so it can deal with misbehaving implementations ๐
To avoid looping, an endpoint MUST NOT send a RST_STREAM in response to a RST_STREAM frame. no ping-pong, please ๐
5.4.3. Connection Termination ๐
If the TCP connection gets closed or reset while streams are still in the "open" or "half-closed" states, then the affected streams can't be automatically retried (see Section 8.7 for details) ๐
5.5. Extending HTTP/2 โจ
HTTP/2 lets you extend the protocol, okay?! ๐ Within the limits in this section, extensions can be used to add extra services or change pretty much any part of the protocol ๐ฆ But extensions only work inside a single HTTP/2 connection, so they never leak past it ๐ซถ
This is about the protocol elements defined in this document. It does NOT touch the regular ways of extending HTTP itself, like defining new methods, status codes, or fields (see Section 16 of [HTTP]). Those are still totally fine ๐ธ
Extensions are allowed to use new frame types (Section 4.1), new settings (Section 6.5), or new error codes (Section 7). The registries for managing these extension points are defined in Section 11 of [RFC7540]. ๐
Implementations MUST ignore unknown or unsupported values in all extensible protocol elements. Implementations MUST discard frames that have unknown or unsupported types. This means any of these extension points can be safely used by extensions without prior arrangement or negotiation. ๐ However, extension frames that show up in the middle of a field block (Section 4.3) are not permitted. These MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. ๐
Extensions SHOULD avoid changing protocol elements defined in this document, or elements for which no extension mechanism is defined. That includes changes to the layout of frames, additions or changes to the way frames are composed into HTTP messages (Section 8.1), the definition of pseudo-header fields, or changes to any protocol element that a compliant endpoint might treat as a connection error (Section 5.4.1). Be gentle with the core stuff, bestie ๐ฅบ
An extension that changes existing protocol elements or state MUST be negotiated before it's used. For example, an extension that changes the layout of the HEADERS frame can't be used until the peer has given a positive signal that this is acceptable. ๐ญ In that case, it could also be necessary to coordinate when the revised layout takes effect. For example, treating frames other than DATA frames as flow controlled requires a change in semantics that both endpoints need to understand, so this can only be done through negotiation. ๐
This document doesn't mandate a specific method for negotiating an extension, but it notes that a setting (Section 6.5.2) could be used for that purpose. If both peers set a value that indicates willingness to use the extension, then the extension can be used. โจ If a setting is used for extension negotiation, the initial value MUST be defined in such a way that the extension is initially disabled. Main character energy: start off, then opt in ๐
6. Frame Definitions ๐
This specification defines a bunch of frame types, each identified by a unique 8-bit type code ๐ง Each frame type has its own job, whether it's setting up or managing the connection as a whole or individual streams ๐
Sending certain frame types can change the state of a connection. If endpoints stop keeping a synchronized view of the connection state, communication within the connection stops working. ๐ So it's super important that endpoints share an understanding of how each frame affects the state ๐ซถ
6.1. DATA ๐
DATA frames (type=0x00) carry arbitrary, variable-length sequences of octets associated with a stream. One or more DATA frames are used, for instance, to carry HTTP request or response message contents. ๐ฆ
DATA frames MAY also contain padding. Padding can be added to DATA frames to obscure the size of messages. Padding is a security feature; see Section 10.7. ๐
DATA Frame {
Length (24),
Type (8) = 0x00,
Unused Flags (4),
PADDED Flag (1),
Unused Flags (2),
END_STREAM Flag (1),
Reserved (1),
Stream Identifier (31),
[Pad Length (8)],
Data (..),
Padding (..2040),
}
Figure 3: DATA Frame Format
The Length, Type, Unused Flag(s), Reserved, and Stream Identifier fields are described in Section 4. The DATA frame has these extra fields:
- Pad Length: An 8-bit field holding the length of the frame padding in units of octets. This field is conditional, so it's only there if the PADDED flag is set. ๐
- Data: Application data. The amount of data is whatever's left of the frame payload after you subtract the length of the other fields that are present. ๐
- Padding: Padding octets that carry no application semantic value. Padding octets MUST be set to zero when sending. A receiver is not obligated to verify padding, but it MAY treat non-zero padding as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. ๐
The DATA frame defines these flags:
- PADDED (0x08): When set, the PADDED flag indicates that the Pad Length field and any padding it describes are present. ๐ธ
- END_STREAM (0x01): When set, the END_STREAM flag indicates that this frame is the last one the endpoint will send for the identified stream. Setting this flag causes the stream to enter one of the "half-closed" states or the "closed" state (Section 5.1). ๐ฆ
Note: An endpoint that learns of stream closure after sending all data can close a stream by sending a STREAM frame with a zero-length Data field and the END_STREAM flag set. This is only possible if the endpoint does not send trailers, as the END_STREAM flag appears on a HEADERS frame in that case; see Section 8.1. ๐
DATA frames MUST be associated with a stream. If a DATA frame is received whose Stream Identifier field is 0x00, the recipient MUST respond with a connection error (Section 5.4.1) of type PROTOCOL_ERROR. ๐ซ
DATA frames are subject to flow control and can only be sent when a stream is in the "open" or "half-closed (remote)" state. The entire DATA frame payload is included in flow control, including the Pad Length and Padding fields if present. If a DATA frame is received whose stream is not in the "open" or "half-closed (local)" state, the recipient MUST respond with a stream error (Section 5.4.2) of type STREAM_CLOSED. ๐
The total number of padding octets is determined by the value of the Pad Length field. If the length of the padding is the length of the frame payload or greater, the recipient MUST treat this as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. ๐ค
Note: A frame can be increased in size by one octet by including a Pad Length field with a value of zero. ๐งธ
6.2. HEADERS ๐
The HEADERS frame (type=0x01) is used to open a stream (Section 5.1), and it also carries a field block fragment. Despite the name, a HEADERS frame can carry a header section or a trailer section. HEADERS frames can be sent on a stream in the "idle", "reserved (local)", "open", or "half-closed (remote)" state. ๐
HEADERS Frame {
Length (24),
Type (8) = 0x01,
Unused Flags (2),
PRIORITY Flag (1),
Unused Flag (1),
PADDED Flag (1),
END_HEADERS Flag (1),
Unused Flag (1),
END_STREAM Flag (1),
Reserved (1),
Stream Identifier (31),
[Pad Length (8)],
[Exclusive (1)],
[Stream Dependency (31)],
[Weight (8)],
Field Block Fragment (..),
Padding (..2040),
}
Figure 4: HEADERS Frame Format
The Length, Type, Unused Flag(s), Reserved, and Stream Identifier fields are described in Section 4. The HEADERS frame payload has these extra fields:
- Pad Length: An 8-bit field holding the length of the frame padding in units of octets. This field is only there if the PADDED flag is set. ๐ธ
- Exclusive: A single-bit flag. This field is only there if the PRIORITY flag is set. Priority signals in HEADERS frames are deprecated, so see Section 5.3.2. ๐ซ
- Stream Dependency: A 31-bit stream identifier. This field is only there if the PRIORITY flag is set. ๐ข
- Weight: An unsigned 8-bit integer. This field is only there if the PRIORITY flag is set. โ๏ธ
- Field Block Fragment: A field block fragment (Section 4.3). ๐งฉ
- Padding: Padding octets that carry no application semantic value. Padding octets MUST be set to zero when sending. A receiver is not obligated to verify padding, but it MAY treat non-zero padding as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. ๐
The HEADERS frame defines these flags:
- PRIORITY (0x20): When set, the PRIORITY flag indicates that the Exclusive, Stream Dependency, and Weight fields are present. ๐ฆ
- PADDED (0x08): When set, the PADDED flag indicates that the Pad Length field and any padding it describes are present. ๐ธ
- END_HEADERS (0x04): When set, the END_HEADERS flag indicates that this frame contains an entire field block (Section 4.3) and is not followed by any CONTINUATION frames. ๐
A HEADERS frame without the END_HEADERS flag set MUST be followed by a CONTINUATION frame for the same stream. A receiver MUST treat the receipt of any other type of frame, or a frame on a different stream, as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. ๐จ
- END_STREAM (0x01): When set, the END_STREAM flag indicates that the field block (Section 4.3) is the last one the endpoint will send for the identified stream.
A HEADERS frame with the END_STREAM flag set signals the end of a stream. However, a HEADERS frame with the END_STREAM flag set can be followed by CONTINUATION frames on the same stream. Logically, the CONTINUATION frames are part of the HEADERS frame. ๐งต
The frame payload of a HEADERS frame contains a field block fragment (Section 4.3). A field block that doesn't fit within a HEADERS frame is continued in a CONTINUATION frame (Section 6.10). ๐จ
HEADERS frames MUST be associated with a stream. If a HEADERS frame is received whose Stream Identifier field is 0x00, the recipient MUST respond with a connection error (Section 5.4.1) of type PROTOCOL_ERROR. ๐
The HEADERS frame changes the connection state as described in Section 4.3. ๐
The total number of padding octets is determined by the value of the Pad Length field. If the length of the padding is the length of the frame payload or greater, the recipient MUST treat this as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. ๐ค
Note: A frame can be increased in size by one octet by including a Pad Length field with a value of zero. ๐งธ
6.3. PRIORITY
okay bestie, the PRIORITY frame (type=0x02) is officially deprecated ๐, so check out Section 5.3.2 for the newer way of doing things. ๐ฆ The good news is that a PRIORITY frame can be sent in any stream state, even on idle or closed streams, which is kind of iconic of it. โจ
PRIORITY Frame {
Length (24) = 0x05,
Type (8) = 0x02,
Unused Flags (8),
Reserved (1),
Stream Identifier (31),
Exclusive (1),
Stream Dependency (31),
Weight (8),
}
Figure 5: PRIORITY Frame Format
The Length, Type, Unused Flag(s), Reserved, and Stream Identifier fields work exactly the way they do in Section 4 ๐. The payload of a PRIORITY frame has a few extra fields on top of that:
Exclusive: a single-bit flag. ๐ง
Stream Dependency: a 31-bit stream identifier. ๐ธ
Weight: an unsigned 8-bit integer. ๐
The PRIORITY frame doesn't define any flags, so no flag drama here ๐ .
The PRIORITY frame always identifies a stream. If one shows up with a stream identifier of 0x00, the recipient MUST respond with a connection error (Section 5.4.1) of type PROTOCOL_ERROR. That is a hard no, periodt ๐.
Sending or receiving a PRIORITY frame doesn't change the state of any stream (Section 5.1), which is lowkey the best part ๐ซถ. It can be sent on a stream in any state, including "idle" or "closed". One thing to remember: a PRIORITY frame cannot be sent between consecutive frames that make up a single field block (Section 4.3) ๐ฅ.
A PRIORITY frame with a length other than 5 octets MUST be treated as a stream error (Section 5.4.2) of type FRAME_SIZE_ERROR. Wrong size, no vibes โจ.
6.4. RST_STREAM
The RST_STREAM frame (type=0x03) is how you immediately end a stream ๐ . It gets sent to request that a stream be cancelled, or to say that something went wrong ๐.
RST_STREAM Frame {
Length (24) = 0x04,
Type (8) = 0x03,
Unused Flags (8),
Reserved (1),
Stream Identifier (31),
Error Code (32),
}
Figure 6: RST_STREAM Frame Format
The Length, Type, Unused Flag(s), Reserved, and Stream Identifier fields are described in Section 4 ๐ธ. On top of those, the RST_STREAM frame carries a single unsigned, 32-bit integer for the error code (Section 7), which tells you why the stream is getting terminated ๐ญ.
The RST_STREAM frame doesn't define any flags. Simple, clean, iconic ๐.
The RST_STREAM frame fully terminates the stream it points at, and the stream moves into the "closed" state. Once a receiver gets a RST_STREAM on a stream, it MUST NOT send any more frames for that stream, except for PRIORITY. The sending endpoint, though, MUST be ready to receive and process any frames the peer sent before the RST_STREAM showed up ๐ซถ. Things cross in the mail, it happens.
RST_STREAM frames MUST be associated with a stream. If one is received with a stream identifier of 0x00, the recipient MUST treat it as a connection error (Section 5.4.1) of type PROTOCOL_ERROR ๐.
RST_STREAM frames MUST NOT be sent for a stream in the "idle" state. If a RST_STREAM frame that identifies an idle stream is received, the recipient MUST treat it as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. Not on my watch ๐.
A RST_STREAM frame with a length other than 4 octets MUST be treated as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR ๐.
6.5. SETTINGS
The SETTINGS frame (type=0x04) is where endpoints share their configuration parameters, like preferences and limits on what the peer does ๐ง. It's also how you acknowledge that you got the settings. Each individual parameter in a SETTINGS frame is called a "setting" ๐.
Settings aren't negotiated ๐. They describe the sending peer, and the receiving peer uses them. Each peer can advertise different values for the same setting, which is giving independent main character energy. For example, a client might set a high initial flow-control window, while a server might pick a lower one to save resources ๐ฆ.
A SETTINGS frame MUST be sent by both endpoints at the start of a connection, and it MAY be sent by either endpoint at any other point over the life of the connection โจ. Implementations MUST support every setting defined in this specification ๐.
Each parameter in a SETTINGS frame replaces whatever value that parameter had before ๐ . Settings get processed in the order they show up, and a receiver doesn't need to keep track of anything except the current value of each setting. So the value of a SETTINGS parameter is just the last one the receiver saw ๐.
SETTINGS frames get acknowledged by the receiving peer ๐ซถ. To make that possible, the SETTINGS frame defines the ACK flag:
ACK (0x01): when set, the ACK flag says that this frame acknowledges receipt and application of the peer's SETTINGS frame. When this bit is set, the frame payload of the SETTINGS frame MUST be empty. Receiving a SETTINGS frame with the ACK flag set and a length field value other than 0 MUST be treated as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR. For more details, check out Section 6.5.3 ("Settings Synchronization") ๐ฅ.
SETTINGS frames always apply to the whole connection, never just one stream ๐ธ. The stream identifier for a SETTINGS frame MUST be zero (0x00). If an endpoint gets a SETTINGS frame whose Stream Identifier field is anything other than 0x00, the endpoint MUST respond with a connection error (Section 5.4.1) of type PROTOCOL_ERROR ๐.
The SETTINGS frame affects connection state. A badly formed or incomplete SETTINGS frame MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR ๐.
A SETTINGS frame with a length that isn't a multiple of 6 octets MUST be treated as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR ๐ญ.
6.5.1. SETTINGS Format
6.5.1. SETTINGS Format
The payload of a SETTINGS frame is zero or more settings, and each one is an unsigned 16-bit setting identifier paired with an unsigned 32-bit value ๐.
SETTINGS Frame {
Length (24),
Type (8) = 0x04,
Unused Flags (7),
ACK Flag (1),
Reserved (1),
Stream Identifier (31) = 0,
Setting (48) ...,
}
Setting {
Identifier (16),
Value (32),
}
Figure 7: SETTINGS Frame Format
The Length, Type, Unused Flag(s), Reserved, and Stream Identifier fields are described in Section 4 ๐ง. The SETTINGS frame payload can hold any number of Setting fields, and each one has:
Identifier: a 16-bit setting identifier; see Section 6.5.2 ๐.
Value: a 32-bit value for the setting ๐.
6.5.2. Defined Settings
6.5.2. Defined Settings
Here are the settings that are defined ๐:
SETTINGS_HEADER_TABLE_SIZE (0x01): this lets the sender tell the remote endpoint the maximum size of the compression table used to decode field blocks, in units of octets ๐ธ. The encoder can pick any size up to and including this value, using signaling specific to the compression format inside a field block (see [COMPRESSION]). The initial value is 4,096 octets ๐.
SETTINGS_ENABLE_PUSH (0x02): this setting can be used to turn server push on or off ๐ฆ. A server MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a value of 0; see Section 8.4. A client that has set this parameter to 0 and had it acknowledged MUST treat receiving a PUSH_PROMISE frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR ๐.
The initial value of SETTINGS_ENABLE_PUSH is 1. For a client, that means it's willing to receive PUSH_PROMISE frames ๐ซถ. For a server, this initial value has no effect and is equivalent to the value 0. Any value other than 0 or 1 MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR ๐
.
A server MUST NOT explicitly set this value to 1. A server MAY choose to leave this setting out when it sends a SETTINGS frame, but if a server does include a value, it MUST be 0. A client MUST treat receiving a SETTINGS frame with SETTINGS_ENABLE_PUSH set to 1 as a connection error (Section 5.4.1) of type PROTOCOL_ERROR ๐ฅ.
SETTINGS_MAX_CONCURRENT_STREAMS (0x03): this setting says how many streams the sender will allow to be open at once ๐. The limit is directional, so it covers the number of streams the sender lets the receiver create. At first there's no limit at all. It is recommended that this value be no smaller than 100, so you don't limit parallelism for no reason โจ.
A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be treated as special by endpoints. A zero value does stop new streams from being created, but that can also happen with any limit that's used up by active streams ๐ง. Servers SHOULD only set a zero value for short durations. If a server doesn't want to accept requests, closing the connection is the better move ๐.
SETTINGS_INITIAL_WINDOW_SIZE (0x04): this setting says what the sender's initial window size is for stream-level flow control, in units of octets ๐ฆ. The initial value is 2^16-1 (65,535) octets ๐ธ.
This setting affects the window size of all streams (see Section 6.9.2) ๐.
Values above the maximum flow-control window size of 2^31-1 MUST be treated as a connection error (Section 5.4.1) of type FLOW_CONTROL_ERROR ๐ญ.
SETTINGS_MAX_FRAME_SIZE (0x05): this setting says how big the largest frame payload is that the sender is willing to receive, in units of octets ๐.
The initial value is 2^14 (16,384) octets. The value an endpoint advertises MUST be between this initial value and the maximum allowed frame size (2^24-1 or 16,777,215 octets), inclusive. Values outside that range MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR ๐.
SETTINGS_MAX_HEADER_LIST_SIZE (0x06): this is an advisory setting that tells a peer the maximum field section size the sender is ready to accept, in units of octets ๐. The value is based on the uncompressed size of field lines, which counts the length of the name and value in octets plus an overhead of 32 octets for each field line ๐ .
For any given request, a lower limit than the one advertised MAY be enforced. The initial value of this setting is unlimited ๐.
An endpoint that receives a SETTINGS frame with any unknown or unsupported identifier MUST ignore that setting ๐ซถ.
6.5.3. Settings Synchronization โจ
Okay so, SETTINGS values are lowkey tricky, bestie. Most of them either benefit from or straight up need to know when the other side has actually received and applied the new value ๐ง So to create a clear "okay, we're synced" moment, the recipient of a SETTINGS frame where the ACK flag is not set MUST apply the updated settings as soon as possible upon receipt. SETTINGS frames get acknowledged in the same order they arrived ๐ซถ
The values in the SETTINGS frame MUST be processed in the order they appear, with no other frame processing between values. Unsupported settings MUST be ignored. Once every value has been processed, the recipient MUST immediately emit a SETTINGS frame with the ACK flag set. When the sender of the altered settings gets a SETTINGS frame with the ACK flag set, it can rely on the values from the oldest unacknowledged SETTINGS frame having been applied ๐
If the sender of a SETTINGS frame doesn't get an acknowledgment within a reasonable amount of time, it MAY issue a connection error (Section 5.4.1) of type SETTINGS_TIMEOUT ๐ญ When you set a timeout, you have to leave some room for processing delays at the peer. A timeout based only on the round-trip time between endpoints might throw spurious errors, and nobody wants that drama ๐
6.6. PUSH_PROMISE
The PUSH_PROMISE frame (type=0x05) is how an endpoint gives its peer a heads-up about streams it plans to start. It includes the unsigned 31-bit identifier of the stream the endpoint intends to create, plus a field section that gives extra context for that stream ๐ฆ Section 8.4 has the full story on how PUSH_PROMISE frames get used.
PUSH_PROMISE Frame {
Length (24),
Type (8) = 0x05,
Unused Flags (4),
PADDED Flag (1),
END_HEADERS Flag (1),
Unused Flags (2),
Reserved (1),
Stream Identifier (31),
[Pad Length (8)],
Reserved (1),
Promised Stream ID (31),
Field Block Fragment (..),
Padding (..2040),
}
Figure 8: PUSH_PROMISE Frame Format
The Length, Type, Unused Flag(s), Reserved, and Stream Identifier fields are all covered in Section 4. On top of those, the PUSH_PROMISE payload has these extra fields:
Pad Length: An 8-bit field with the length of the frame padding, measured in octets. It only shows up if the PADDED flag is set ๐
Promised Stream ID: An unsigned 31-bit integer naming the stream that this PUSH_PROMISE reserves. The promised stream identifier MUST be a valid choice for the next stream the sender starts (see "new stream identifier" in Section 5.1.1). Main character stream energy ๐
Field Block Fragment: A field block fragment (Section 4.3) carrying the request control data and a header section ๐ฆ
Padding: Padding octets with no application semantic value. Padding octets MUST be set to zero when sending. A receiver doesn't have to verify padding, but it MAY treat non-zero padding as a connection error (Section 5.4.1) of type PROTOCOL_ERROR ๐
The PUSH_PROMISE frame defines these flags:
PADDED (0x08): When set, this flag says the Pad Length field and whatever padding it describes are present ๐ธ
END_HEADERS (0x04): When set, this flag says the frame carries an entire field block (Section 4.3), and no CONTINUATION frames come after it ๐
A PUSH_PROMISE frame without the END_HEADERS flag set MUST be followed by a CONTINUATION frame for the same stream. If a receiver gets any other type of frame, or a frame on a different stream, it MUST treat that as a connection error (Section 5.4.1) of type PROTOCOL_ERROR ๐ซ
PUSH_PROMISE frames MUST only be sent on a peer-initiated stream that is in either the "open" or "half-closed (remote)" state. The stream identifier of a PUSH_PROMISE frame tells you which stream it's tied to. If the Stream Identifier field is 0x00, a recipient MUST respond with a connection error (Section 5.4.1) of type PROTOCOL_ERROR ๐
Promised streams don't have to be used in the same order they were promised. PUSH_PROMISE only reserves stream identifiers so they can be used later ๐ง
PUSH_PROMISE MUST NOT be sent if the SETTINGS_ENABLE_PUSH setting of the peer endpoint is set to 0. An endpoint that has set this setting and has received acknowledgment MUST treat the receipt of a PUSH_PROMISE frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR โ
Recipients of PUSH_PROMISE frames can choose to reject promised streams. To do that, they send a RST_STREAM referencing the promised stream identifier back to the sender of the PUSH_PROMISE ๐ โโ๏ธ
A PUSH_PROMISE frame changes the connection state in two ways. First, including a field block (Section 4.3) can change the state kept for field section compression ๐๏ธ Second, PUSH_PROMISE reserves a stream for later use, which moves the promised stream into the "reserved (local)" or "reserved (remote)" state. A sender MUST NOT send a PUSH_PROMISE on a stream unless that stream is either "open" or "half-closed (remote)". The sender MUST also make sure the promised stream is a valid choice for a new stream identifier (Section 5.1.1), which means the promised stream MUST be in the "idle" state โจ
Since PUSH_PROMISE reserves a stream, ignoring a PUSH_PROMISE frame leaves the stream state indeterminate. A receiver MUST treat the receipt of a PUSH_PROMISE on a stream that is neither "open" nor "half-closed (local)" as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. There's one exception: an endpoint that has sent RST_STREAM on the associated stream MUST still handle PUSH_PROMISE frames that might have been created before the RST_STREAM frame was received and processed ๐ซฃ
A receiver MUST treat the receipt of a PUSH_PROMISE that promises an illegal stream identifier (Section 5.1.1) as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. Quick reminder: an illegal stream identifier is one for a stream that is not currently in the "idle" state ๐จ
The total number of padding octets is set by the Pad Length field. If the padding length is the length of the frame payload or greater, the recipient MUST treat this as a connection error (Section 5.4.1) of type PROTOCOL_ERROR ๐
Note: A frame can be increased in size by one octet by including a Pad Length field with a value of zero. ๐
6.7. PING
The PING frame (type=0x06) is a mechanism for measuring a minimal round-trip time from the sender, and for checking whether an idle connection is still working ๐ PING frames can be sent from any endpoint ๐
PING Frame {
Length (24) = 0x08,
Type (8) = 0x06,
Unused Flags (7),
ACK Flag (1),
Reserved (1),
Stream Identifier (31) = 0,
Opaque Data (64),
}
Figure 9: PING Frame Format
The Length, Type, Unused Flag(s), Reserved, and Stream Identifier fields are described in Section 4.
On top of the frame header, PING frames MUST contain 8 octets of opaque data in the frame payload. The sender can put any value it likes in there and use those octets in any way it wants ๐ฒ
Receivers of a PING frame that doesn't include an ACK flag MUST send back a PING frame with the ACK flag set, carrying an identical frame payload. PING responses SHOULD be given higher priority than any other frame ๐
The PING frame defines this flag:
ACK (0x01): When set, this flag says the PING frame is a PING response. An endpoint MUST set this flag in PING responses. An endpoint MUST NOT respond to PING frames that contain this flag ๐
PING frames aren't tied to any individual stream. If a PING frame arrives with a Stream Identifier field value other than 0x00, the recipient MUST respond with a connection error (Section 5.4.1) of type PROTOCOL_ERROR ๐
If a PING frame arrives with a length field value other than 8, that MUST be treated as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR ๐
6.8. GOAWAY ๐ฆ
okay so the GOAWAY frame (type=0x07) is the "bye bestie, we're wrapping up" frame ๐ It's used to start shutting down a connection or to flag a serious error ๐จ. GOAWAY lets an endpoint gracefully stop accepting new streams while it finishes the ones it already started ๐
which is super useful for admin stuff like server maintenance ๐ง.
ngl there's a little race condition here ๐ฌ An endpoint can start new streams at the exact same moment the other side sends a GOAWAY. So the GOAWAY includes the stream identifier of the last peer-initiated stream that was or might be processed by the sending endpoint on this connection. For example, if the server sends GOAWAY, the identified stream is the highest-numbered stream the client started.
Once the GOAWAY is sent, the sender will ignore frames on streams started by the receiver if those streams have an identifier higher than the last stream identifier it included. Receivers of a GOAWAY frame MUST NOT open additional streams on the connection ๐ โโ๏ธ but a new connection is totally fine for new streams โจ
If the receiver of the GOAWAY has sent data on streams with a higher stream identifier than the one in the GOAWAY frame, those streams are not or will not be processed. The receiver can treat those streams as if they were never created at all ๐ช which means they can be retried later on a new connection.
Endpoints SHOULD always send a GOAWAY frame before closing a connection ๐ so the other side knows whether a stream was partially processed. Picture this: an HTTP client sends a POST at the exact same time the server closes the connection. If the server never sent a GOAWAY to say which streams it might have acted on, the client has no idea whether that POST got started ๐ญ
An endpoint might choose to close a connection without sending a GOAWAY for misbehaving peers.
A GOAWAY frame might not come right before the connection closes. Even so, a receiver of a GOAWAY that has no more use for the connection SHOULD still send a GOAWAY frame before terminating the connection ๐ธ
GOAWAY Frame {
Length (24),
Type (8) = 0x07,
Unused Flags (8),
Reserved (1),
Stream Identifier (31) = 0,
Reserved (1),
Last-Stream-ID (31),
Error Code (32),
Additional Debug Data (..),
}
Figure 10: GOAWAY Frame Format
The Length, Type, Unused Flag(s), Reserved, and Stream Identifier fields are described in Section 4.
The GOAWAY frame doesn't define any flags ๐ซ๐ฉ
The GOAWAY frame applies to the whole connection, not one specific stream. An endpoint MUST treat a GOAWAY frame with a stream identifier other than 0x00 as a connection error (Section 5.4.1) of type PROTOCOL_ERROR ๐ that's a big no.
The last stream identifier in the GOAWAY frame is the highest-numbered stream identifier the sender might have acted on, or might still act on ๐ฏ Every stream up to and including that one might have been processed in some way. If no streams were processed, the last stream identifier can be set to 0.
Note: In this context, "processed" means that some data from the stream was passed to some higher layer of software that might have taken some action as a result.
If a connection ends without a GOAWAY frame, the last stream identifier is effectively the highest possible stream identifier ๐
For streams with lower or equal identifiers that weren't completely closed before the connection closed, you can't retry requests, transactions, or any protocol activity... except for idempotent actions like HTTP GET, PUT, or DELETE ๐ Any protocol activity on higher-numbered streams can be safely retried on a new connection ๐ฆ
Activity on streams numbered lower than or equal to the last stream identifier might still complete successfully ๐ฅน The sender of a GOAWAY frame can gracefully shut down a connection by sending a GOAWAY frame, keeping the connection in an "open" state until all the in-progress streams finish.
An endpoint MAY send multiple GOAWAY frames if circumstances change. For example, an endpoint that sends GOAWAY with NO_ERROR during a graceful shutdown could later run into something that requires terminating the connection right now ๐ณ The last stream identifier from the last GOAWAY frame received tells you which streams could have been acted upon. Endpoints MUST NOT increase the value they send in the last stream identifier, because peers might already have retried unprocessed requests on another connection ๐
โโ๏ธ
A client that can't retry requests loses everything in flight when the server closes the connection ๐ This is especially true for intermediaries that might not be serving clients using HTTP/2. A server trying to gracefully shut down a connection SHOULD send an initial GOAWAY frame with the last stream identifier set to 2^31-1 and a NO_ERROR code ๐ซถ This tells the client a shutdown is coming and that starting new requests is prohibited. After giving in-flight stream creation some time (at least one round-trip time), the server MAY send another GOAWAY frame with an updated last stream identifier. That's how a connection gets cleanly shut down without losing requests โจ
After sending a GOAWAY frame, the sender can discard frames for streams started by the receiver with identifiers higher than the last stream identified. However, frames that change connection state can't be totally ignored ๐ For example, HEADERS, PUSH_PROMISE, and CONTINUATION frames MUST be minimally processed so the state kept for field section compression stays consistent (see Section 4.3). Likewise, DATA frames MUST be counted toward the connection flow-control window. If you skip processing these frames, flow control or field section compression state can become unsynchronized ๐ตโ๐ซ
The GOAWAY frame also has a 32-bit error code (Section 7) that explains why the connection is closing ๐งพ
Endpoints MAY append opaque data to the frame payload of any GOAWAY frame ๐ Additional debug data is only for diagnostics and carries no semantic value. But debug info could include security- or privacy-sensitive data ๐ So logged or otherwise persistently stored debug data MUST have adequate safeguards to prevent unauthorized access ๐
6.9. WINDOW_UPDATE ๐
The WINDOW_UPDATE frame (type=0x08) is how flow control gets implemented ๐ฐ (see Section 5.2 for the overview).
Flow control works at two levels: on each individual stream, and on the entire connection ๐
Both types of flow control are hop by hop, meaning they only happen between the two endpoints. Intermediaries do not forward WINDOW_UPDATE frames between dependent connections ๐ โโ๏ธ That said, if any receiver throttles its data transfer, that can indirectly push flow-control information back toward the original sender ๐
Flow control only applies to frames that are identified as being subject to flow control. Of the frame types defined in this document, that's only DATA frames ๐ฏ Frames that are exempt from flow control MUST be accepted and processed, unless the receiver can't assign resources to handle the frame. A receiver MAY respond with a stream error (Section 5.4.2) or a connection error (Section 5.4.1) of type FLOW_CONTROL_ERROR if it can't accept a frame ๐ค
WINDOW_UPDATE Frame {
Length (24) = 0x04,
Type (8) = 0x08,
Unused Flags (8),
Reserved (1),
Stream Identifier (31),
Reserved (1),
Window Size Increment (31),
}
Figure 11: WINDOW_UPDATE Frame Format
The Length, Type, Unused Flag(s), Reserved, and Stream Identifier fields are described in Section 4. The frame payload of a WINDOW_UPDATE frame is one reserved bit plus an unsigned 31-bit integer that tells you how many octets the sender can transmit on top of the existing flow-control window ๐ The legal range for the increment is 1 to 2^31-1 (2,147,483,647) octets ๐ข
The WINDOW_UPDATE frame doesn't define any flags ๐ซ๐ฉ
A WINDOW_UPDATE frame can be specific to a stream or to the entire connection. For a stream, the frame's stream identifier says which stream is affected. For the whole connection, the value 0 means the entire connection is the subject of the frame ๐
A receiver MUST treat receiving a WINDOW_UPDATE frame with a flow-control window increment of 0 as a stream error (Section 5.4.2) of type PROTOCOL_ERROR. Errors on the connection flow-control window MUST be treated as a connection error (Section 5.4.1) ๐จ
A peer can send WINDOW_UPDATE after it has sent a frame with the END_STREAM flag set ๐ So a receiver could get a WINDOW_UPDATE on a stream that's in a "half-closed (remote)" or "closed" state. A receiver MUST NOT treat this as an error (see Section 5.1) ๐
A receiver that gets a flow-controlled frame MUST always account for its contribution against the connection flow-control window, unless the receiver treats it as a connection error (Section 5.4.1) ๐งฎ This is necessary even if the frame is in error. The sender counts the frame toward the flow-control window, so if the receiver doesn't, the sender's and receiver's flow-control windows can end up different ๐ฌ
A WINDOW_UPDATE frame with a length other than 4 octets MUST be treated as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR ๐ bestie, measure twice ๐ซถ
6.9.1. The Flow-Control Window ๐ช
okay so flow control in HTTP/2 works like this: every sender keeps a little window on every stream ๐. The flow-control window is just a simple integer that says how many octets of data the sender is allowed to send right now, so its size is basically a measure of how much buffer room the receiver has ๐งบโจ
There are two windows in play: the stream flow-control window and the connection flow-control window ๐ฏโโ๏ธ. The sender MUST NOT send a flow-controlled frame with a length bigger than the space left in either of the flow-control windows the receiver advertised ๐
โโ๏ธ. Frames with zero length and the END_STREAM flag set (so, an empty DATA frame) MAY be sent even when there's no space left in either window ๐
For flow-control math, the 9-octet frame header doesn't count ๐งฎ
After the sender sends a flow-controlled frame, it takes that frame's length off the space left in both windows ๐
Now the receiver is the one who keeps things moving ๐โโ๏ธ. As it consumes data and frees up space in its windows, it sends a WINDOW_UPDATE frame ๐. It sends separate WINDOW_UPDATE frames for the stream-level and connection-level windows, because they're tracked separately ๐
. Receivers are advised to have mechanisms in place so they don't send WINDOW_UPDATE frames with very tiny increments, see Section 4.2.3.3 of [RFC1122] ๐
When a sender gets a WINDOW_UPDATE frame, it bumps the matching window up by whatever amount the frame says ๐
Here's the big one, though ๐จ. A sender MUST NOT let a flow-control window go past 2^31-1 octets. If a sender gets a WINDOW_UPDATE that pushes a window over that max, it MUST shut down either the stream or the connection, depending on which one it is ๐. For a stream, the sender sends a RST_STREAM with the error code FLOW_CONTROL_ERROR. For the whole connection, it sends a GOAWAY frame with the error code FLOW_CONTROL_ERROR ๐ญ
Last but not least, flow-controlled frames from the sender and WINDOW_UPDATE frames from the receiver are totally asynchronous with respect to each other ๐ฆ. That's the magic part, because it lets a receiver update the sender's window aggressively so that streams don't get stuck ๐งโจ
6.9.2. Initial Flow-Control Window Size ๐ธ
When an HTTP/2 connection is first set up, new streams start with an initial flow-control window size of 65,535 octets ๐. The connection flow-control window also starts at 65,535 octets ๐. Both endpoints can change the initial window size for new streams by including a value for SETTINGS_INITIAL_WINDOW_SIZE in a SETTINGS frame ๐ง. The connection flow-control window, though, can only be changed with WINDOW_UPDATE frames ๐
Until an endpoint receives a SETTINGS frame that sets SETTINGS_INITIAL_WINDOW_SIZE, it can only use the default initial window size when it sends flow-controlled frames ๐ผ. Same deal for the connection window: it's based on the default initial window size until a WINDOW_UPDATE frame shows up ๐ซถ
A SETTINGS frame can do more than just change the window for streams that haven't started yet ๐คฏ. It can also change the initial window size for streams that already have active windows (streams in the "open" or "half-closed (remote)" state) โจ. When the value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of every stream flow-control window it keeps by the difference between the new value and the old value ๐งฎ
Heads up, this can get spicy ๐ถ๏ธ. A change to SETTINGS_INITIAL_WINDOW_SIZE can make the space left in a flow-control window go negative. A sender MUST keep track of that negative window, and MUST NOT send any new flow-controlled frames until it gets WINDOW_UPDATE frames that make the window positive again ๐
Here's an example, because examples are everything ๐. Say the client sends 60 KB right when the connection starts, and the server sets the initial window size to 16 KB. When the client gets that SETTINGS frame, it recalculates its available flow-control window to be -44 KB ๐ณ. It just sits with that negative window until WINDOW_UPDATE frames bring it back to positive, and then it can resume sending ๐ฆ
A SETTINGS frame cannot alter the connection flow-control window ๐
โโ๏ธ
An endpoint MUST treat any change to SETTINGS_INITIAL_WINDOW_SIZE that makes any flow-control window go over the maximum size as a connection error (Section 5.4.1) of type FLOW_CONTROL_ERROR ๐ซ
6.9.3. Reducing the Stream Window Size ๐ช
If a receiver wants a smaller flow-control window than the current one, it can send a new SETTINGS frame ๐. But here's the catch: the receiver MUST be ready to receive data that goes past this smaller window, because the sender might've already sent data over the lower limit before it processed the SETTINGS frame ๐ฌ
After sending a SETTINGS frame that shrinks the initial flow-control window size, a receiver MAY keep processing streams that go over their flow-control limits ๐คทโโ๏ธ. Letting those streams keep going doesn't mean the receiver can instantly free up the space it reserved for its flow-control windows ๐. Progress on those streams can stall too, since the sender needs WINDOW_UPDATE frames before it can start sending again ๐ง. The receiver MAY instead send a RST_STREAM with the error code FLOW_CONTROL_ERROR for the affected streams ๐
6.10. CONTINUATION ๐งต
The CONTINUATION frame (type=0x09) is used to continue a sequence of field block fragments (Section 4.3) ๐ชก. You can send any number of CONTINUATION frames, as long as the frame before it is on the same stream and is a HEADERS, PUSH_PROMISE, or CONTINUATION frame without the END_HEADERS flag set ๐ซถ
CONTINUATION Frame {
Length (24),
Type (8) = 0x09,
Unused Flags (5),
END_HEADERS Flag (1),
Unused Flags (2),
Reserved (1),
Stream Identifier (31),
Field Block Fragment (..),
}
Figure 12: CONTINUATION Frame Format ๐
The Length, Type, Unused Flag(s), Reserved, and Stream Identifier fields are described in Section 4 ๐. The CONTINUATION frame payload carries a field block fragment (Section 4.3) โจ
The CONTINUATION frame defines just one flag ๐ฉ:
END_HEADERS(0x04): When set, theEND_HEADERSflag says that this frame ends a field block (Section 4.3) ๐.- If the
END_HEADERSflag is not set, this frame MUST be followed by anotherCONTINUATIONframe. A receiver MUST treat the receipt of any other type of frame, or a frame on a different stream, as a connection error (Section 5.4.1) of typePROTOCOL_ERROR๐จ.
- If the
The CONTINUATION frame changes the connection state the way Section 4.3 describes ๐
CONTINUATION frames MUST be associated with a stream ๐ฏ. If a CONTINUATION frame arrives with a Stream Identifier field of 0x00, the recipient MUST respond with a connection error (Section 5.4.1) of type PROTOCOL_ERROR ๐ญ
A CONTINUATION frame MUST come right after a HEADERS, PUSH_PROMISE, or CONTINUATION frame without the END_HEADERS flag set ๐. A recipient that spots a violation of this rule MUST respond with a connection error (Section 5.4.1) of type PROTOCOL_ERROR ๐
7. Error Codes ๐จ
Error codes are 32-bit fields used in RST_STREAM and GOAWAY frames to explain why a stream or connection error happened ๐ฃ
All error codes share one common code space ๐. Some of them only apply to streams or only to the whole connection, and they have no defined meaning in the other context ๐
Here are the error codes that are defined ๐:
NO_ERROR(0x00): The condition isn't the result of an error at all ๐ฅณ. For example, aGOAWAYmight include this code to show a graceful shutdown of a connection ๐ท.PROTOCOL_ERROR(0x01): The endpoint spotted an unspecific protocol error ๐ค. This one is for when there's no more specific error code available ๐ซ .INTERNAL_ERROR(0x02): The endpoint ran into an unexpected internal error ๐ฅ.FLOW_CONTROL_ERROR(0x03): The endpoint noticed that its peer broke the flow-control protocol ๐ค.SETTINGS_TIMEOUT(0x04): The endpoint sent aSETTINGSframe but didn't get a response in a timely way ๐ฐ๏ธ. See Section 6.5.3 ("Settings Synchronization") ๐.STREAM_CLOSED(0x05): The endpoint got a frame after a stream was half-closed ๐ช.FRAME_SIZE_ERROR(0x06): The endpoint got a frame with an invalid size ๐.REFUSED_STREAM(0x07): The endpoint refused the stream before doing any application processing (see Section 8.7 for details) ๐ โโ๏ธ.CANCEL(0x08): The endpoint uses this code to say the stream isn't needed anymore ๐.COMPRESSION_ERROR(0x09): The endpoint can't maintain the field section compression context for the connection ๐๏ธ.CONNECT_ERROR(0x0a): The connection set up in response to aCONNECTrequest (Section 8.5) got reset or closed abnormally ๐.ENHANCE_YOUR_CALM(0x0b): The endpoint noticed its peer is doing something that might be creating excessive load ๐ฎโ๐จ.INADEQUATE_SECURITY(0x0c): The underlying transport has properties that don't meet the minimum security requirements (see Section 9.2) ๐.HTTP_1_1_REQUIRED(0x0d): The endpoint needs HTTP/1.1 to be used instead of HTTP/2 ๐.
Unknown or unsupported error codes MUST NOT trigger any special behavior ๐
โโ๏ธ. These MAY be treated by an implementation as equivalent to INTERNAL_ERROR ๐ซถ
8. Expressing HTTP Semantics in HTTP/2 ๐
HTTP/2 is an instantiation of the HTTP message abstraction (Section 6 of [HTTP]) ๐ฆ
8.1. HTTP Message Framing ๐
Okay bestie, so a client sends an HTTP request on a new stream, using a previously unused stream identifier (Section 5.1.1). Then the server sends its HTTP response back on that same stream as the request ๐
An HTTP message (request or response) is made of three possible parts ๐ฆ:
- one HEADERS frame (followed by zero or more CONTINUATION frames) containing the header section (see Section 6.3 of [HTTP]),
- zero or more DATA frames containing the message content (see Section 6.4 of [HTTP]), and
- optionally, one HEADERS frame (followed by zero or more CONTINUATION frames) containing the trailer section, if present (see Section 6.5 of [HTTP]).
For a response only, a server MAY send any number of interim responses before the HEADERS frame containing a final response. An interim response is a HEADERS frame (which might be followed by zero or more CONTINUATION frames) holding the control data and header section of an interim (1xx) HTTP response (see Section 15 of [HTTP]). A HEADERS frame with the END_STREAM flag set that carries an informational status code is malformed (Section 8.1.1). Not me sending a 1xx and then a flag that says "done" ๐
The last frame in the sequence bears an END_STREAM flag, and a HEADERS frame with the END_STREAM flag set can be followed by CONTINUATION frames that carry any remaining fragments of the field block. That's the whole vibe of the last frame, ngl โจ
Other frames (from any stream) MUST NOT occur between the HEADERS frame and any CONTINUATION frames that might follow. No sneaking a frame in the middle, periodt ๐
HTTP/2 uses DATA frames to carry message content. The chunked transfer encoding defined in Section 7.1 of [HTTP/1.1] cannot be used in HTTP/2; see Section 8.2.2. Chunked encoding is a no-go here, sorry not sorry ๐ โโ๏ธ
Trailer fields are carried in a field block that also terminates the stream. That means trailer fields are a sequence that starts with a HEADERS frame, followed by zero or more CONTINUATION frames, where the HEADERS frame bears an END_STREAM flag. Trailers MUST NOT include pseudo-header fields (Section 8.3). An endpoint that receives pseudo-header fields in trailers MUST treat the request or response as malformed (Section 8.1.1). Pseudo-headers in trailers? Absolutely not, that's a crime against the spec ๐
An endpoint that receives a HEADERS frame without the END_STREAM flag set after receiving the HEADERS frame that opens a request, or after receiving a final (non-informational) status code, MUST treat the corresponding request or response as malformed (Section 8.1.1). Main character energy means you check this stuff ๐
An HTTP request/response exchange fully consumes a single stream. A request starts with the HEADERS frame that puts the stream into the "open" state. The request ends with a frame with the END_STREAM flag set, which causes the stream to become "half-closed (local)" for the client and "half-closed (remote)" for the server. A response stream starts with zero or more interim responses in HEADERS frames, followed by a HEADERS frame containing a final status code. Stream states are giving very structured, I love a clean lifecycle ๐ธ
An HTTP response is complete after the server sends (or the client receives) a frame with the END_STREAM flag set (including any CONTINUATION frames needed to complete a field block). A server can send a complete response before the client sends the entire request, if the response does not depend on any portion of the request that has not been sent and received. When this is true, a server MAY request that the client abort transmission of a request without error by sending a RST_STREAM with an error code of NO_ERROR after sending a complete response (i.e., a frame with the END_STREAM flag set). Clients MUST NOT discard responses as a result of receiving such a RST_STREAM, though clients can always discard responses at their discretion for other reasons. Okay the server basically says "stop sending me stuff, I already answered," and that's so polite it's iconic ๐ซถ
8.1.1. Malformed Messages ๐ง
A malformed request or response is one that is an otherwise valid sequence of HTTP/2 frames, but is invalid due to the presence of extraneous frames, prohibited fields or pseudo-header fields, the absence of mandatory pseudo-header fields, the inclusion of uppercase field names, or invalid field names and/or values (in certain circumstances; see Section 8.2). So many ways to be malformed, bestie, be careful out there ๐ญ
A request or response that includes message content can include a content-length header field. A request or response is also malformed if the value of a content-length header field does not equal the sum of the DATA frame payload lengths that form the content, unless the message is defined as having no content. For example, 204 or 304 responses contain no content, as does the response to a HEAD request. A response that is defined to have no content, as described in Section 6.4.1 of [HTTP], MAY have a non-zero content-length header field, even though no content is included in DATA frames. ๐
Intermediaries that process HTTP requests or responses (i.e., any intermediary not acting as a tunnel) MUST NOT forward a malformed request or response. Malformed requests or responses that are detected MUST be treated as a stream error (Section 5.4.2) of type PROTOCOL_ERROR. Intermediaries gotta be the bouncers of the internet, no malformed messages allowed in the club ๐
For malformed requests, a server MAY send an HTTP response prior to closing or resetting the stream. Clients MUST NOT accept a malformed response. ๐
Endpoints that progressively process messages might have done some processing before identifying a request or response as malformed. For instance, it might be possible to generate an informational or 404 status code without having received a complete request. Similarly, intermediaries might forward incomplete messages before detecting errors. A server MAY generate a final response before receiving an entire request when the response does not depend on the remainder of the request being correct. Processing as you go is so efficient, it's giving multitasking queen โญ
These requirements are intended to protect against several types of common attacks against HTTP. They are deliberately strict, because being permissive can expose implementations to these vulnerabilities. Strict on purpose, because permissive is how implementations get hacked. Protect yourself, bestie ๐ฅ
8.2. HTTP Fields ๐
HTTP fields (Section 5 of [HTTP]) are conveyed by HTTP/2 in the HEADERS, CONTINUATION, and PUSH_PROMISE frames, compressed with HPACK [COMPRESSION]. Fields ride along in HEADERS, CONTINUATION, and PUSH_PROMISE frames, all squished down with HPACK ๐ฆ
Field names MUST be converted to lowercase when constructing an HTTP/2 message. Lowercase only, no exceptions, it's the rule ๐
8.2.1. Field Validity ๐
The definitions of field names and values in HTTP prohibit some characters that HPACK might be able to convey. HTTP/2 implementations SHOULD validate field names and values according to their definitions in Sections 5.1 and 5.5 of [HTTP], respectively, and treat messages that contain prohibited characters as malformed (Section 8.1.1). HPACK will happily carry things HTTP says no to, so you gotta check ๐
Failure to validate fields can be exploited for request smuggling attacks. In particular, unvalidated fields might enable attacks when messages are forwarded using HTTP/1.1 [HTTP/1.1], where characters such as carriage return (CR), line feed (LF), and COLON are used as delimiters. Implementations MUST perform the following minimal validation of field names and values:
- A field name MUST NOT contain characters in the ranges
0x00-0x20,0x41-0x5a, or0x7f-0xff(all ranges inclusive). This specifically excludes all non-visible ASCII characters, ASCII SP (0x20), and uppercase characters ('A' to 'Z', ASCII0x41to0x5a). - With the exception of pseudo-header fields (Section 8.3), which have a name that starts with a single colon, field names MUST NOT include a colon (ASCII COLON,
0x3a). - A field value MUST NOT contain the zero value (ASCII NUL,
0x00), line feed (ASCII LF,0x0a), or carriage return (ASCII CR,0x0d) at any position. - A field value MUST NOT start or end with an ASCII whitespace character (ASCII SP or HTAB,
0x20or0x09).
Note: An implementation that validates fields according to the definitions in Sections 5.1 and 5.5 of [HTTP] only needs an additional check that field names do not include uppercase characters.
A request or response that contains a field that violates any of these conditions MUST be treated as malformed (Section 8.1.1). In particular, an intermediary that does not process fields when forwarding messages MUST NOT forward fields that contain any of the values that are listed as prohibited above. Smuggling? Not on my watch ๐
When a request message violates one of these requirements, an implementation SHOULD generate a 400 (Bad Request) status code (see Section 15.5.1 of [HTTP]), unless a more suitable status code is defined or the status code cannot be sent (e.g., because the error occurs in a trailer field). A 400 is the polite "nope, try again" the server should send, unless something better fits ๐ธ
Note: Field values that are not valid according to the definition of the corresponding field do not cause a request to be malformed. The requirements above only apply to the generic syntax for fields as defined in Section 5 of [HTTP]. ๐ง
8.2.2. Connection-Specific Header Fields ๐
HTTP/2 does not use the Connection header field (Section 7.6.1 of [HTTP]) to indicate connection-specific header fields. In this protocol, connection-specific metadata is conveyed by other means. An endpoint MUST NOT generate an HTTP/2 message containing connection-specific header fields. This includes the Connection header field and those listed as having connection-specific semantics in Section 7.6.1 of [HTTP] (that is, Proxy-Connection, Keep-Alive, Transfer-Encoding, and Upgrade). Any message containing connection-specific header fields MUST be treated as malformed (Section 8.1.1). No connection-specific headers in HTTP/2, babe. Those are so last decade ๐
The only exception to this is the TE header field, which MAY be present in an HTTP/2 request. When it is, it MUST NOT contain any value other than "trailers". ๐ฆ
An intermediary transforming an HTTP/1.x message to HTTP/2 MUST remove connection-specific header fields as discussed in Section 7.6.1 of [HTTP], or their messages will be treated by other HTTP/2 endpoints as malformed (Section 8.1.1). Translators have to clean up the old headers or the whole message gets bounced, no cap ๐ซถ
Note: HTTP/2 purposefully does not support upgrade to another protocol. The handshake methods described in Section 3 are believed sufficient to negotiate the use of alternative protocols. โญ
8.2.3. Compressing the Cookie Header Field ๐ช
The Cookie header field [COOKIE] uses a semicolon (";") to delimit cookie-pairs (or "crumbs"). This header field contains multiple values, but does not use a COMMA (",") as a separator, thereby preventing cookie-pairs from being sent on multiple field lines (see Section 5.2 of [HTTP]). This can significantly reduce compression efficiency, as updates to individual cookie-pairs would invalidate any field lines that are stored in the HPACK table. Cookies being all lumped together is cute, but it makes compression sad when one crumb changes ๐ญ
To allow for better compression efficiency, the Cookie header field MAY be split into separate header fields, each with one or more cookie-pairs. If there are multiple Cookie header fields after decompression, these MUST be concatenated into a single octet string using the two-octet delimiter of 0x3b, 0x20 (the ASCII string "; ") before being passed into a non-HTTP/2 context, such as an HTTP/1.1 connection, or a generic HTTP server application. โจ
Therefore, the following two lists of Cookie header fields are semantically equivalent. ๐
cookie: a=b; c=d; e=f
cookie: a=b
cookie: c=d
cookie: e=f
8.3. HTTP Control Data โจ
bestie, okay so HTTP/2 needs a way to carry the control stuff for a message, like which method, where it's going, and what status came back. It does this with special pseudo-header fields whose names start with a : (ASCII 0x3a) ๐
. The HTTP semantics bit is explained in Section 6.2 of [HTTP] ๐ธ.
Pseudo-header fields are NOT regular HTTP header fields, so don't mix them up ๐. Endpoints MUST NOT generate any pseudo-header fields other than the ones defined in this document. An extension could negotiate extra ones, though, so check Section 5.5 if that's your vibe ๐ฆ.
They're also super picky about where they show up. Request-only pseudo-header fields MUST NOT appear in responses, and response-only ones MUST NOT appear in requests ๐ซ. Pseudo-header fields MUST NOT appear in a trailer section either. If an endpoint sees a request or response with undefined or invalid pseudo-header fields, it MUST treat that message as malformed (Section 8.1.1) ๐.
Ordering matters too! ALL pseudo-header fields MUST appear in a field block before any regular field lines. If a pseudo-header shows up after a regular field line, that request or response MUST be treated as malformed (Section 8.1.1) ๐ญ. And the same pseudo-header field name MUST NOT appear more than once in a field block. A request or response with a repeated pseudo-header field name MUST be treated as malformed (Section 8.1.1) ๐.
8.3.1. Request Pseudo-Header Fields ๐
HTTP/2 requests get these pseudo-header fields:
- The
:methodpseudo-header field carries the HTTP method (Section 9 of [HTTP]) ๐. - The
:schemepseudo-header field carries the scheme part of the request target. When you're generating a request directly, the scheme comes from the target URI (Section 3.1 of [RFC3986]). If it's a translated request, it comes from the scheme of that translated request (for example, see Section 3.3 of [HTTP/1.1]).:schemeis omitted for CONNECT requests (Section 8.5) ๐ง.
:scheme is NOT limited to http and https URIs, which is so iconic ๐. A proxy or gateway can translate requests for non-HTTP schemes, so you can use HTTP to talk to non-HTTP services. Main character energy โจ.
- The
:authoritypseudo-header field carries the authority part (Section 3.2 of [RFC3986]) of the target URI (Section 7.1 of [HTTP]) ๐ฆ. Now the drama: the recipient of an HTTP/2 request MUST NOT use theHostheader field to figure out the target URI if:authorityis present ๐ โโ๏ธ.
Clients that generate HTTP/2 requests directly MUST use :authority to convey authority information, unless there's no authority information to send. In that case, they MUST NOT generate :authority ๐ซถ.
Clients MUST NOT generate a request with a Host header field that differs from :authority. A server SHOULD treat a request as malformed if its Host header field identifies a different entity than :authority does. To compare the values, you gotta normalise them (see Section 6.2 of [RFC3986]). An origin server can apply any normalisation method it likes, whereas other servers MUST perform scheme-based normalisation (see Section 6.2.3 of [RFC3986]) on the two fields ๐.
An intermediary that forwards a request over HTTP/2 MUST build :authority from the authority information in the control data of the original request. The exception is when the original request's target URI has no authority information, and then the intermediary MUST NOT generate :authority ๐ธ. Also, the Host header field isn't the only place this info lives, so peep Section 7.2 of [HTTP] too ๐.
An intermediary that needs to generate a Host header field (which might be needed to build an HTTP/1.1 request) MUST use the value from :authority as the value of Host, unless the intermediary also changes the request target. This replaces any existing Host field, which helps avoid potential vulnerabilities in HTTP routing ๐ก๏ธ.
An intermediary that forwards a request over HTTP/2 MAY keep any Host header field it already has ๐.
Note that request targets for CONNECT or asterisk-form OPTIONS requests NEVER include authority information (see Sections 7.1 and 7.2 of [HTTP]) ๐.
And lastly, :authority MUST NOT include the deprecated userinfo subcomponent for http or https schemed URIs ๐ซ.
- The
:pathpseudo-header field carries the path and query parts of the target URI. That means the absolute-path production, and optionally a?followed by the query production (see Section 4.1 of [HTTP]) ๐บ๏ธ. A request in asterisk form (for OPTIONS) uses the value'*'for:path๐ซ.
:path MUST NOT be empty for http or https URIs. http or https URIs without a path component MUST include a value of '/'. There are two exceptions:
- An OPTIONS request for an
httporhttpsURI that has no path component. These MUST include a:pathpseudo-header field with the value'*'(see Section 7.1 of [HTTP]) ๐. - CONNECT requests (Section 8.5), where
:pathis omitted ๐.
All HTTP/2 requests MUST include exactly one valid value each for :method, :scheme, and :path, unless they're CONNECT requests (Section 8.5) ๐. An HTTP request that leaves out mandatory pseudo-header fields is malformed (Section 8.1.1) ๐.
Individual HTTP/2 requests don't carry an explicit protocol version indicator. All HTTP/2 requests implicitly have a protocol version of "2.0" (see Section 6.2 of [HTTP]) ๐.
8.3.2. Response Pseudo-Header Fields ๐ท
For HTTP/2 responses there's one pseudo-header field defined, :status, which carries the HTTP status code field (see Section 15 of [HTTP]) ๐. :status MUST be included in ALL responses, interim responses included. Otherwise the response is malformed (Section 8.1.1) ๐ญ.
HTTP/2 responses implicitly have a protocol version of "2.0" too โจ.
8.4. Server Push ๐
HTTP/2 lets a server preemptively send (or "push") responses to a client, along with corresponding "promised" requests, in association with an earlier client-initiated request ๐.
Server push was designed to make client-perceived performance faster by predicting which requests will follow the ones it receives, which removes a round trip for them ๐โโ๏ธ. For example, a request for HTML is often followed by requests for the stylesheets and scripts that page references. When those get pushed, the client doesn't have to wait to see the references in the HTML and then send separate requests ๐จ.
Real talk though, server push is hard to use well ๐ฉ. The server has to correctly guess the extra requests the client will make, which depends on stuff like caching, content negotiation, and user behavior. Wrong guesses can slow things down because of the opportunity cost that the extra data on the wire represents. Pushing a big pile of data can cause contention with responses that matter more ๐.
A client can ask for server push to be disabled, but this is negotiated for each hop independently ๐. The SETTINGS_ENABLE_PUSH setting can be set to 0 to say server push is disabled ๐.
Promised requests MUST be safe (see Section 9.2.1 of [HTTP]) and cacheable (see Section 9.2.3 of [HTTP]). Promised requests can't include any content or a trailer section ๐
โโ๏ธ. Clients that get a promised request that's not cacheable, not known to be safe, or that indicates request content is present MUST reset the promised stream with a stream error (Section 5.4.2) of type PROTOCOL_ERROR ๐ฅ. Heads up, this could reset the promised stream if the client doesn't recognise a newly defined method as safe ๐.
Pushed responses that are cacheable (see Section 3 of [CACHING]) can be stored by the client, if it implements an HTTP cache ๐ฆ. Pushed responses count as successfully validated on the origin server (for example, if the "no-cache" cache response directive is present; see Section 5.2.2.4 of [CACHING]) while the stream identified by the promised stream identifier is still open โณ.
Pushed responses that are NOT cacheable MUST NOT be stored by any HTTP cache. They MAY be made available to the application separately ๐ซง.
The server MUST include a value in the :authority pseudo-header field that it's authoritative for (see Section 10.1). A client MUST treat a PUSH_PROMISE for which the server isn't authoritative as a stream error (Section 5.4.2) of type PROTOCOL_ERROR ๐จ.
An intermediary can receive pushes from the server and choose not to forward them to the client. In other words, how to use the pushed info is up to that intermediary ๐ง . Also, the intermediary might decide to make extra pushes to the client without the server doing anything ๐.
A client can't push. So servers MUST treat receiving a PUSH_PROMISE frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR ๐ฅ. And a server can't set SETTINGS_ENABLE_PUSH to anything other than 0 (see Section 6.5.2) ๐
โโ๏ธ๐
.
8.4.1. Push Requests โจ
Okay so server push is basically the server answering a request ๐ , except this time the server also sends that request itself, inside a PUSH_PROMISE frame ๐ง. It's giving "I already know what you're gonna ask for" energy.
The PUSH_PROMISE frame carries a field block with control data and a complete set of request header fields that the server attributes to the request. It is not possible to push a response to a request that includes message content ๐ โโ๏ธ.
Promised requests are always tied to an explicit request from the client. The PUSH_PROMISE frames the server sends go on that explicit request's stream ๐ฆ. The PUSH_PROMISE frame also includes a promised stream identifier, picked from the stream identifiers the server is allowed to use (see Section 5.1.1).
The header fields in PUSH_PROMISE and any CONTINUATION frames after it MUST be a valid and complete set of request header fields (Section 8.3.1). The server MUST include a method in the :method pseudo-header field that is safe and cacheable ๐ฅ. If a client gets a PUSH_PROMISE that doesn't include a complete and valid set of header fields, or the :method pseudo-header field names a method that isn't safe, it MUST respond on the promised stream with a stream error (Section 5.4.2) of type PROTOCOL_ERROR ๐.
The server SHOULD send PUSH_PROMISE (Section 6.6) frames before sending any frames that reference the promised responses. This avoids a race where clients fire off requests before they've even received any PUSH_PROMISE frames ๐โโ๏ธ๐จ.
For example, say the server gets a request for a document with embedded links to a bunch of image files, and it decides to push those images. Sending the PUSH_PROMISE frames before the DATA frames with the image links means the client can see that a resource is getting pushed before it finds the embedded links ๐ธ. Same vibe if the server pushes resources referenced in the field block (like in Link header fields): sending a PUSH_PROMISE before the header means clients won't go requesting those resources themselves ๐ซถ.
PUSH_PROMISE frames MUST NOT be sent by the client. Absolutely not, bestie ๐ โโ๏ธ.
PUSH_PROMISE frames can be sent by the server on any client-initiated stream, but the stream MUST be in either the "open" or "half-closed (remote)" state with respect to the server. PUSH_PROMISE frames get interspersed with the frames that make up a response, but they can't be interspersed with the HEADERS and CONTINUATION frames that make up a single field block ๐.
Sending a PUSH_PROMISE frame creates a new stream and puts it into the "reserved (local)" state for the server and the "reserved (remote)" state for the client โจ.
8.4.2. Push Responses ๐
After sending the PUSH_PROMISE frame, the server can start delivering the pushed response as a response (Section 8.3.2) on a server-initiated stream that uses the promised stream identifier ๐. The server uses this stream to send an HTTP response, using the same sequence of frames defined in Section 8.1. This stream becomes "half-closed" to the client (Section 5.1) once the initial HEADERS frame is sent.
Once a client receives a PUSH_PROMISE frame and decides to accept the pushed response, the client SHOULD NOT issue any requests for the promised response until after the promised stream has closed ๐. Don't double-request, babe.
If the client decides, for any reason, that it doesn't want the pushed response, or if the server takes too long to start sending the promised response, the client can send a RST_STREAM frame. It can use either the CANCEL or REFUSED_STREAM code and reference the pushed stream's identifier ๐ซ.
A client can use the SETTINGS_MAX_CONCURRENT_STREAMS setting to limit how many responses a server can push at once ๐. Advertising a SETTINGS_MAX_CONCURRENT_STREAMS value of zero stops the server from opening the streams it needs to push responses. BUT, and this is the plot twist, it doesn't stop the server from reserving streams using PUSH_PROMISE frames, because reserved streams don't count toward the concurrent stream limit ๐ญ. So clients who don't want pushed resources need to reset any unwanted reserved streams, or set SETTINGS_ENABLE_PUSH to 0 ๐
.
Clients receiving a pushed response MUST validate that either the server is authoritative (see Section 10.1) or the proxy that provided the pushed response is configured for the corresponding request ๐. For example, a server that only offers a certificate for the example.com DNS-ID (see [RFC6125]) is not permitted to push a response for <https://www.example.org/doc>. Not the vibe ๐
โโ๏ธ.
The response for a PUSH_PROMISE stream begins with a HEADERS frame, which immediately puts the stream into the "half-closed (remote)" state for the server and the "half-closed (local)" state for the client. It ends with a frame that has the END_STREAM flag set, which puts the stream in the "closed" state ๐ฆ.
Note: The client never sends a frame with the END_STREAM flag set for a server push. ๐ซถ
8.5. The CONNECT Method ๐
The CONNECT method (Section 9.3.6 of [HTTP]) turns an HTTP connection into a tunnel to a remote host ๐. It's mostly used with HTTP proxies to set up a TLS session with an origin server so you can interact with "https" resources ๐.
In HTTP/2, the CONNECT method sets up a tunnel over a single HTTP/2 stream to a remote host, instead of converting the whole connection into a tunnel ๐งโโ๏ธ. A CONNECT header section is built as defined in Section 8.3.1 ("Request Pseudo-Header Fields"), with a few differences. Specifically:
- The
:methodpseudo-header field is set toCONNECT. - The
:schemeand:pathpseudo-header fields MUST be omitted. - The
:authoritypseudo-header field contains the host and port to connect to. This is equivalent to the authority-form of the request-target of CONNECT requests (see Section 3.2.3 of [HTTP/1.1]).
A CONNECT request that doesn't follow these restrictions is malformed (Section 8.1.1) ๐.
A proxy that supports CONNECT establishes a TCP connection [TCP] to the host and port identified in the :authority pseudo-header field ๐ค๏ธ. Once that connection is successfully established, the proxy sends a HEADERS frame containing a 2xx-series status code to the client, as defined in Section 9.3.6 of [HTTP] โจ.
After the initial HEADERS frame sent by each peer, all subsequent DATA frames correspond to data sent on the TCP connection ๐. The frame payload of any DATA frames the client sends gets transmitted by the proxy to the TCP server, and data received from the TCP server gets assembled into DATA frames by the proxy. Frame types other than DATA or stream management frames (RST_STREAM, WINDOW_UPDATE, and PRIORITY) MUST NOT be sent on a connected stream, and they MUST be treated as a stream error (Section 5.4.2) if received ๐ซ.
The TCP connection can be closed by either peer ๐. The END_STREAM flag on a DATA frame is treated as being equivalent to the TCP FIN bit. A client is expected to send a DATA frame with the END_STREAM flag set after receiving a frame with the END_STREAM flag set. A proxy that receives a DATA frame with the END_STREAM flag set sends the attached data with the FIN bit set on the last TCP segment. A proxy that receives a TCP segment with the FIN bit set sends a DATA frame with the END_STREAM flag set. Note that the final TCP segment or DATA frame could be empty ๐ฅน.
A TCP connection error is signaled with RST_STREAM. A proxy treats any error in the TCP connection, including receiving a TCP segment with the RST bit set, as a stream error (Section 5.4.2) of type CONNECT_ERROR ๐ญ. Correspondingly, a proxy MUST send a TCP segment with the RST bit set if it detects an error with the stream or the HTTP/2 connection ๐จ.
8.6. The Upgrade Header Field ๐
HTTP/2 does not support the 101 (Switching Protocols) informational status code (Section 15.2.2 of [HTTP]). Sorry, not sorry ๐คทโโ๏ธ.
The semantics of 101 (Switching Protocols) aren't applicable to a multiplexed protocol ๐. Similar functionality might be enabled through the use of extended CONNECT [RFC8441], and other protocols can use the same mechanisms that HTTP/2 uses to negotiate their use (see Section 3) ๐ง.
8.7. Request Reliability ๐ก๏ธ
In general, an HTTP client can't retry a non-idempotent request when an error happens, because there's no way to tell what kind of error it was (see Section 9.2.2 of [HTTP]) ๐ฐ. It's possible that some server processing happened before the error, and retrying could cause some seriously undesirable effects ๐.
HTTP/2 gives clients two ways to be guaranteed that a request hasn't been processed:
- The GOAWAY frame indicates the highest stream number that might have been processed. Requests on streams with higher numbers are therefore guaranteed to be safe to retry โจ.
- The
REFUSED_STREAMerror code can be included in a RST_STREAM frame to indicate that the stream is being closed before any processing has occurred. Any request that was sent on the reset stream can be safely retried ๐ซถ.
Requests that haven't been processed haven't failed. Clients MAY automatically retry them, even the ones with non-idempotent methods ๐.
A server MUST NOT indicate that a stream has not been processed unless it can guarantee that fact ๐ค. If frames that are on a stream are passed to the application layer for any stream, then REFUSED_STREAM MUST NOT be used for that stream, and a GOAWAY frame MUST include a stream identifier that is greater than or equal to the given stream identifier ๐ซ.
Oh, and in addition to all of that, the PING frame gives a client an easy way to test a connection ๐. Connections that sit idle can break, because some middleboxes (for instance, network address translators or load balancers) silently discard connection bindings ๐ป. The PING frame lets a client safely check whether a connection is still active without sending a request ๐.
8.8. Examples ๐
This section shows HTTP/1.1 requests and responses, with illustrations of the equivalent HTTP/2 requests and responses ๐.
8.8.1. Simple Request ๐ง
An HTTP GET request includes control data and a request header with no message content, so it's sent as a single HEADERS frame, followed by zero or more CONTINUATION frames containing the serialized block of request header fields ๐ฆ. The HEADERS frame below has both the END_HEADERS and END_STREAM flags set, so no CONTINUATION frames are sent โจ.
GET /resource HTTP/1.1 HEADERS
Host: example.org ==> + END_STREAM
Accept: image/jpeg + END_HEADERS
:method = GET
:scheme = https
:authority = example.org
:path = /resource
host = example.org
accept = image/jpeg
8.8.2. Simple Response ๐
Okay so ๐ a response that only has control data and a response header gets sent as a HEADERS frame too, which can be followed by zero or more CONTINUATION frames, and it carries the serialized block of response header fields. โจ
HTTP/1.1 304 Not Modified HEADERS
ETag: "xyzzy" ==> + END_STREAM
Expires: Thu, 23 Jan ... + END_HEADERS
:status = 304
etag = "xyzzy"
expires = Thu, 23 Jan ...
8.8.3. Complex Request ๐
An HTTP POST request with control data and a request header that includes message content gets sent as one HEADERS frame. After that come zero or more CONTINUATION frames holding the request header, and then one or more DATA frames. The last CONTINUATION (or HEADERS) frame has the END_HEADERS flag set, and the final DATA frame has the END_STREAM flag set. ๐ฆ
POST /resource HTTP/1.1 HEADERS
Host: example.org ==> - END_STREAM
Content-Type: image/jpeg - END_HEADERS
Content-Length: 123 :method = POST
:authority = example.org
:path = /resource
{binary data} :scheme = https
CONTINUATION
+ END_HEADERS
content-type = image/jpeg
host = example.org
content-length = 123
DATA
+ END_STREAM
{binary data}
Quick heads up, bestie ๐ซถ: data for any single field line can be split across field block fragments. The way the fields get divided between frames in this example is just for illustration, so don't treat it as the only way to do it. ๐
8.8.4. Response with Body ๐
A response with control data and a response header that includes message content gets sent as a HEADERS frame, then zero or more CONTINUATION frames, then one or more DATA frames. The last DATA frame in the sequence has the END_STREAM flag set. ๐ญ
HTTP/1.1 200 OK HEADERS
Content-Type: image/jpeg ==> - END_STREAM
Content-Length: 123 + END_HEADERS
:status = 200
{binary data} content-type = image/jpeg
content-length = 123
DATA
+ END_STREAM
{binary data}
8.8.5. Informational Responses ๐ธ
An informational response with a 1xx status code other than 101 is sent as a HEADERS frame, followed by zero or more CONTINUATION frames. โจ
A trailer section gets sent as a field block after both the request or response field block and all the DATA frames have already gone out. The HEADERS frame that kicks off the field block making up the trailer section has the END_STREAM flag set. ๐ง
This example has both a 100 (Continue) status code, which is sent in response to a request containing a "100-continue" token in the Expect header field, and a trailer section. It's giving full stack protocol energy ๐ฅ
HTTP/1.1 100 Continue HEADERS
Extension-Field: bar ==> - END_STREAM
+ END_HEADERS
:status = 100
extension-field = bar
HTTP/1.1 200 OK HEADERS
Content-Type: image/jpeg ==> - END_STREAM
Transfer-Encoding: chunked + END_HEADERS
Trailer: Foo :status = 200
content-type = image/jpeg
123 trailer = Foo
{binary data}
0 DATA
Foo: bar - END_STREAM
{binary data}
HEADERS
+ END_STREAM
+ END_HEADERS
foo = bar
9. HTTP/2 Connections ๐
This section lays out the HTTP attributes that improve interoperability, cut down on exposure to known security vulnerabilities, or lower the chances of implementations doing their own weird thing. ๐
9.1. Connection Management ๐
HTTP/2 connections are persistent. For the best performance, the expectation is that clients won't close connections until they're sure no more communication with a server is needed (for example, when a user navigates away from a particular web page), or until the server closes the connection. ๐ธ
Clients SHOULD NOT open more than one HTTP/2 connection to a given host and port pair. Here the host is derived from a URI, a selected alternative service [ALT-SVC], or a configured proxy. ๐ โโ๏ธ
A client can create extra connections as replacements. It might do that to replace connections that are close to running out of the available stream identifier space (Section 5.1.1), to refresh the keying material for a TLS connection, or to replace connections that have hit errors (Section 5.4.1). ๐
A client MAY open multiple connections to the same IP address and TCP port using different Server Name Indication [TLS-EXT] values or to provide different TLS client certificates, but it SHOULD avoid creating multiple connections with the same configuration. ๐ญ
Servers are encouraged to keep connections open for as long as possible, but they're allowed to terminate idle connections if necessary. When either endpoint decides to close the transport-layer TCP connection, the terminating endpoint SHOULD first send a GOAWAY (Section 6.8) frame. That way, both endpoints can reliably tell whether previously sent frames have been processed, and they can gracefully finish or end any remaining tasks. ๐ซถ
9.1.1. Connection Reuse ๐ง
Connections made to an origin server, either directly or through a tunnel created with the CONNECT method (Section 8.5), MAY be reused for requests with multiple different URI authority components. A connection can be reused as long as the origin server is authoritative (Section 10.1). For TCP connections without TLS, this depends on the host having resolved to the same IP address. ๐บ๏ธ
For "https" resources, connection reuse also depends on having a certificate that is valid for the host in the URI. The certificate the server presents MUST satisfy any checks the client would perform when forming a new TLS connection for the host in the URI. One certificate can be used to establish authority for multiple origins. Section 4.3 of [HTTP] describes how a client works out whether a server is authoritative for a URI. ๐
In some deployments, reusing a connection for multiple origins can end up sending requests to the wrong origin server. For example, TLS termination might be handled by a middlebox that uses the TLS Server Name Indication [TLS-EXT] extension to pick an origin server. That means clients could send requests to servers that aren't the intended target for the request, even when the server is otherwise authoritative. ๐ฌ
A server that doesn't want clients reusing connections can say it isn't authoritative for a request by sending a 421 (Misdirected Request) status code in response to that request (see Section 15.5.20 of [HTTP]). ๐
A client that is set up to use a proxy over HTTP/2 sends requests to that proxy through a single connection. In other words, every request sent via a proxy reuses the connection to the proxy. ๐
9.2. Use of TLS Features ๐ฅ
Implementations of HTTP/2 MUST use TLS version 1.2 [TLS12] or higher for HTTP/2 over TLS. The general TLS usage guidance in [TLSBCP] SHOULD be followed, along with some extra restrictions that are specific to HTTP/2. ๐
The TLS implementation MUST support the Server Name Indication (SNI) [TLS-EXT] extension to TLS. If the server is identified by a domain name [DNS-TERMS], clients MUST send the server_name TLS extension unless an alternative mechanism to indicate the target host is used. ๐
Requirements for deployments of HTTP/2 that negotiate TLS 1.3 [TLS13] are in Section 9.2.3. Deployments of TLS 1.2 are covered by the requirements in Sections 9.2.1 and 9.2.2. Implementations are encouraged to ship defaults that comply, but it's recognized that deployments are ultimately responsible for compliance. ๐
9.2.1. TLS 1.2 Features ๐
This section describes restrictions on the TLS 1.2 feature set that can be used with HTTP/2. Because of deployment limitations, it might not be possible to fail TLS negotiation when these restrictions aren't met. An endpoint MAY immediately terminate an HTTP/2 connection that doesn't meet these TLS requirements with a connection error (Section 5.4.1) of type INADEQUATE_SECURITY. ๐จ
A deployment of HTTP/2 over TLS 1.2 MUST disable compression. TLS compression can expose information that wouldn't otherwise be revealed [RFC3749]. Generic compression isn't needed, since HTTP/2 has compression features that are more aware of context, which makes them more likely to be appropriate for performance, security, or other reasons. ๐๏ธ
A deployment of HTTP/2 over TLS 1.2 MUST disable renegotiation. An endpoint MUST treat a TLS renegotiation as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. Heads up, though: turning off renegotiation can leave long-lived connections unusable, because of limits on how many messages the underlying cipher suite can encipher. ๐ซ
An endpoint MAY use renegotiation to protect the confidentiality of client credentials offered in the handshake, but any renegotiation MUST occur before the connection preface is sent. A server SHOULD request a client certificate if it sees a renegotiation request right after establishing a connection. ๐ชช
This effectively rules out renegotiation in response to a request for a specific protected resource. A future specification might provide a way to support that use case. Alternatively, a server might use an error (Section 5.4) of type HTTP_1_1_REQUIRED to ask the client to use a protocol that supports renegotiation. ๐ฎ
Implementations MUST support ephemeral key exchange sizes of at least 2048 bits for cipher suites that use ephemeral finite field Diffie-Hellman (DHE) (Section 8.1.2 of [TLS12]), and 224 bits for cipher suites that use ephemeral elliptic curve Diffie-Hellman (ECDHE) [RFC8422]. Clients MUST accept DHE sizes of up to 4096 bits. Endpoints MAY treat negotiation of key sizes smaller than these lower limits as a connection error (Section 5.4.1) of type INADEQUATE_SECURITY. ๐
9.2.2. TLS 1.2 Cipher Suites ๐
Okay bestie, if you're running HTTP/2 over TLS 1.2, you SHOULD NOT use any of the prohibited cipher suites listed in Appendix A. ๐ Yes, that's a lot of capitals, but this rule is serious.
Endpoints MAY choose to generate a connection error (Section 5.4.1) of type INADEQUATE_SECURITY if one of the prohibited cipher suites gets negotiated. ๐ฌ A deployment that goes ahead and uses a prohibited cipher suite risks setting off that connection error, unless it knows every potential peer is fine with that cipher suite. It's giving risky. ๐
Implementations MUST NOT generate this error when a cipher suite that is not prohibited gets negotiated. So when clients offer a cipher suite that isn't prohibited, they have to be ready to use it with HTTP/2. ๐ซถ
Here's the tricky part: the list of prohibited cipher suites includes the one TLS 1.2 makes mandatory. That means TLS 1.2 deployments could end up with permitted cipher suite sets that don't overlap at all, which is a whole mess. ๐ To avoid that handshake-failure drama, HTTP/2 deployments that use TLS 1.2 MUST support TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 [TLS-ECDHE] with the P-256 elliptic curve [RFC8422]. โจ
Also, clients might advertise prohibited cipher suites so they can still connect to servers that don't support HTTP/2. ๐ฆ That lets servers pick HTTP/1.1 with a cipher suite that's prohibited in HTTP/2. Just know that this can end with HTTP/2 negotiated using a prohibited cipher suite, if the application protocol and the cipher suite get selected independently. ๐ญ
9.2.3. TLS 1.3 Features ๐
TLS 1.3 comes with a bunch of features that earlier versions don't have, so this section breaks down how to use them. ๐
HTTP/2 servers MUST NOT send post-handshake TLS 1.3 CertificateRequest messages. HTTP/2 clients MUST treat a TLS post-handshake CertificateRequest message as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. ๐ซ
The ban on post-handshake authentication still applies even if the client offered the post_handshake_auth TLS extension. Post-handshake authentication support might be advertised separately from ALPN [TLS-ALPN]. Clients might offer that capability for use in other protocols, but including the extension can't mean support within HTTP/2. ๐
โโ๏ธ
[TLS13] defines other post-handshake messages, NewSessionTicket and KeyUpdate. These can be used because they don't directly interact with HTTP/2. โจ Unless a new type of TLS message depends on an interaction with the application-layer protocol, that message can be sent once the handshake is done. ๐
TLS early data MAY be used to send requests, as long as you follow the guidance in [RFC8470]. ๐ง Clients send requests in early data while assuming initial values for all server settings.
10. Security Considerations ๐ก๏ธ
Okay, this part is seriously important, so pay attention! ๐จ TLS is necessary to get many of the security properties this protocol promises. Lots of the claims in this section just don't hold up unless TLS is used the way Section 9.2 describes. ๐
10.1. Server Authority ๐
HTTP/2 leans on the HTTP definition of authority to decide whether a server is allowed to give a particular response (see Section 4.3 of [HTTP]). For the http URI scheme, that depends on local name resolution. For the https scheme, it depends on the authenticated server identity. ๐
10.2. Cross-Protocol Attacks ๐ต๏ธโโ๏ธ
In a cross-protocol attack, an attacker tricks a client into starting a transaction in one protocol with a server that actually understands a different protocol. ๐ฑ The attacker might even make that transaction look valid in the second protocol. Combine that with what a web context can do, and it can be used to poke at poorly protected servers sitting in private networks. Yikes.
Finishing a TLS handshake with an ALPN identifier for HTTP/2 can be considered enough protection against cross-protocol attacks. โจ ALPN gives a clear signal that the server is willing to go ahead with HTTP/2, which blocks attacks on other TLS-based protocols. ๐ช
The encryption in TLS makes it hard for attackers to control the data that could be used in a cross-protocol attack on a cleartext protocol. ๐
The cleartext version of HTTP/2 has minimal protection against cross-protocol attacks. The connection preface (Section 3.4) contains a string designed to confuse HTTP/1.1 servers, but no special protection is offered for other protocols. ๐
10.3. Intermediary Encapsulation Attacks ๐งจ
HPACK lets you encode field names and values that might be treated as delimiters in other HTTP versions. ๐ฌ So an intermediary that translates an HTTP/2 request or response MUST validate fields according to the rules in Section 8.2 before translating the message to another HTTP version. If a field has invalid delimiters and gets translated anyway, recipients could misread the message, and an attacker could exploit that. ๐
Section 8.2 doesn't include specific rules for validating pseudo-header fields. If the values of those fields are used, you need extra validation. This matters a lot when :scheme, :authority, and :path get combined into one URI string [RFC3986]. Similar problems might pop up when that URI, or just :path, is combined with :method to build a request line (as in Section 3 of [HTTP/1.1]). Simple concatenation isn't secure unless the input values are fully validated. ๐ซ
An intermediary can also reject fields that have invalid field names or values for other reasons. In particular, it can reject fields that don't follow the HTTP ABNF grammar from Section 5 of [HTTP]. Intermediaries that skip any validation beyond the bare minimum required by Section 8.2 could forward messages with invalid field names or values. ๐
An intermediary that receives any fields needing removal before forwarding (see Section 7.6.1 of [HTTP]) MUST remove or replace those header fields when it forwards messages. ๐งน Also, intermediaries should take care when forwarding messages with Content-Length fields, so the message stays well-formed (Section 8.1.1). That way, if the message gets translated into HTTP/1.1 at any point, the framing will be correct. โ
10.4. Cacheability of Pushed Responses ๐ฆ
Pushed responses don't come from an explicit request by the client. Instead, the server supplies the request in the PUSH_PROMISE frame. ๐ค
Caching pushed responses is possible, based on the guidance the origin server gives in the Cache-Control header field. ๐๏ธ But this can cause problems if a single server hosts more than one tenant. For example, a server might give multiple users each a small slice of its URI space.
Where multiple tenants share space on the same server, that server MUST ensure that tenants can't push representations of resources they have no authority over. ๐ง If it doesn't enforce this, a tenant could provide a representation that gets served out of the cache, overriding the one the authoritative tenant actually provides. Chaos! ๐ฑ
Pushed responses for which an origin server is not authoritative (see Section 10.1) MUST NOT be used or cached. ๐ โโ๏ธ
10.5. Denial-of-Service Considerations ๐
An HTTP/2 connection can demand a bigger commitment of resources to operate than an HTTP/1.1 connection. ๐ป Both field section compression and flow control depend on keeping a larger amount of state. The settings for these features make sure that memory commitments for them are strictly bounded. ๐ง
The number of PUSH_PROMISE frames isn't constrained in the same way. A client that accepts server push SHOULD limit the number of streams it allows to be in the reserved (remote) state. ๐ An excessive number of server push streams can be treated as a stream error (Section 5.4.2) of type ENHANCE_YOUR_CALM. ๐ค
A number of HTTP/2 implementations turned out to be vulnerable to denial of service [NFLX-2019-002]. Here's a list of known ways implementations might be hit by denial-of-service attacks. Let's go! ๐ฅ
- Inefficient tracking of outstanding outbound frames can lead to overload if an adversary can cause large numbers of frames to be enqueued for sending. A peer could use one of several techniques to generate large numbers of frames:
- Giving tiny increments to flow control in
WINDOW_UPDATEframes can make a sender generate a large number ofDATAframes. ๐ - An endpoint is required to respond to a
PINGframe. ๐ - Each
SETTINGSframe requires acknowledgment. โ - An invalid request (or server push) can cause a peer to send
RST_STREAMframes in response. ๐ฅ
- Giving tiny increments to flow control in
- An attacker can give out large amounts of flow-control credit at the HTTP/2 layer but hold back credit at the TCP layer, which stops frames from being sent. An endpoint that builds and remembers frames for sending without considering TCP limits might run into resource exhaustion. ๐ฅต
- Large numbers of small or empty frames can be abused to make a peer spend time processing frame headers. Be careful here, because some uses of small frames are totally legit, like sending an empty
DATAorCONTINUATIONframe at the end of a stream. ๐ซ - The
SETTINGSframe can also be abused to make a peer waste processing time. This might be done by pointlessly changing settings, sending multiple undefined settings, or changing the same setting multiple times in the same frame. ๐ - Handling reprioritization with
PRIORITYframes can take significant processing time and can lead to overload if manyPRIORITYframes get sent. ๐ข - Field section compression also gives attackers chances to waste processing resources. See Section 7 of [COMPRESSION] for more details on potential abuses. ๐๏ธ
- Limits in
SETTINGScan't be reduced instantly, which leaves an endpoint open to behavior from a peer that could exceed the new limits. Right after a connection is established, the limits a server sets aren't known to clients, so they could be exceeded without it being an obvious protocol violation. ๐
Most of the features that could be exploited for denial of service, like SETTINGS changes, small frames, and field section compression, have legitimate uses. They only become a burden when they're used unnecessarily or to excess. ๐
An endpoint that doesn't monitor how these features get used is exposing itself to a risk of denial of service. ๐จ Implementations SHOULD track the use of these features and set limits on their use. An endpoint MAY treat suspicious activity as a connection error (Section 5.4.1) of type ENHANCE_YOUR_CALM. ๐ซถ
10.5.1. Limits on Field Block Size ๐
okay so a big field block (Section 4.3) can make an implementation hold onto a LOT of state ๐ญ. Field lines that matter for routing can sit at the very end of a field block, which means you can't stream fields straight to their ultimate destination ๐. Between that ordering and other stuff like keeping caches correct, an endpoint might have to buffer the entire field block. And since there's no hard limit on how big a field block can get, some endpoints could end up committing a huge chunk of memory just for field blocks ๐.
An endpoint can use SETTINGS_MAX_HEADER_LIST_SIZE to give its peers a heads-up about limits that might apply to the size of uncompressed field blocks ๐ฃ. It's only advisory though, so endpoints MAY choose to send field blocks that go over the limit, and they risk the request or response being treated as malformed ๐. This setting is per connection, so any request or response could run into a hop with a lower limit that nobody knows about ๐ซฃ. An intermediary can try to dodge that problem by passing along values it gets from different peers, but it isn't obliged to do so ๐คทโโ๏ธ.
A server that gets a bigger field block than it's willing to handle can send HTTP 431 (Request Header Fields Too Large) status code [RFC6585] ๐
โโ๏ธ. A client can discard responses it can't process. The field block MUST be processed to make sure the connection state stays consistent, unless the connection is closed ๐.
10.5.2. CONNECT Issues โจ
The CONNECT method can create disproportionate load on a proxy, since stream creation is relatively inexpensive compared to creating and maintaining a TCP connection ๐งฑ. A proxy might also keep some resources around for a TCP connection beyond closing the stream that carries the CONNECT request, since the outgoing TCP connection remains in the TIME_WAIT state โณ. Therefore, a proxy cannot rely on SETTINGS_MAX_CONCURRENT_STREAMS alone to limit the resources consumed by CONNECT requests ๐
.
10.6. Use of Compression ๐๏ธ
Compression can allow an attacker to recover secret data when it is compressed in the same context as data under attacker control ๐. HTTP/2 enables compression of field lines (Section 4.3); the following concerns also apply to the use of HTTP compressed content-codings (Section 8.4.1 of [HTTP]).
There are demonstrable attacks on compression that exploit the characteristics of the Web (e.g., [BREACH]). The attacker induces multiple requests containing varying plaintext, observing the length of the resulting ciphertext in each, which reveals a shorter length when a guess about the secret is correct ๐ฑ.
Implementations communicating on a secure channel MUST NOT compress content that includes both confidential and attacker-controlled data unless separate compression dictionaries are used for each source of data. Compression MUST NOT be used if the source of data cannot be reliably determined. Generic stream compression, such as that provided by TLS, MUST NOT be used with HTTP/2 (see Section 9.2) ๐ซ.
Further considerations regarding the compression of header fields are described in [COMPRESSION] ๐.
10.7. Use of Padding ๐
Padding within HTTP/2 is not intended as a replacement for general purpose padding, such as that provided by TLS [TLS13] ๐ โโ๏ธ. Redundant padding could even be counterproductive. Correct application can depend on having specific knowledge of the data that is being padded.
To mitigate attacks that rely on compression, disabling or limiting compression might be preferable to padding as a countermeasure ๐ง .
Padding can be used to obscure the exact size of frame content and is provided to mitigate specific attacks within HTTP -- for example, attacks where compressed content includes both attacker-controlled plaintext and secret data (e.g., [BREACH]) ๐ญ.
Use of padding can result in less protection than might seem immediately obvious. At best, padding only makes it more difficult for an attacker to infer length information by increasing the number of frames an attacker has to observe. Incorrectly implemented padding schemes can be easily defeated. In particular, randomized padding with a predictable distribution provides very little protection; similarly, padding frame payloads to a fixed size exposes information as frame payload sizes cross the fixed-sized boundary, which could be possible if an attacker can control plaintext ๐ฌ.
Intermediaries SHOULD retain padding for DATA frames but MAY drop padding for HEADERS and PUSH_PROMISE frames. A valid reason for an intermediary to change the amount of padding of frames is to improve the protections that padding provides ๐ก๏ธ.
10.8. Privacy Considerations ๐ต๏ธโโ๏ธ
Several characteristics of HTTP/2 give an observer an opportunity to correlate actions of a single client or server over time ๐๏ธ. These include the values of settings, the manner in which flow-control windows are managed, the way priorities are allocated to streams, the timing of reactions to stimulus, and the handling of any features that are controlled by settings.
As far as these create observable differences in behavior, they could be used as a basis for fingerprinting a specific client, as defined in Section 3.2 of [PRIVACY] ๐.
HTTP/2's preference for using a single TCP connection allows correlation of a user's activity on a site. Reusing connections for different origins allows tracking across those origins ๐ซฃ.
Because the PING and SETTINGS frames solicit immediate responses, they can be used by an endpoint to measure latency to their peer. This might have privacy implications in certain scenarios ๐ธ.
10.9. Remote Timing Attacks โฑ๏ธ
Remote timing attacks extract secrets from servers by observing variations in the time that servers take when processing requests that use secrets ๐ฐ๏ธ. HTTP/2 enables concurrent request creation and processing, which can give attackers better control over when request processing commences. Multiple HTTP/2 requests can be included in the same IP packet or TLS record. HTTP/2 can therefore make remote timing attacks more efficient by eliminating variability in request delivery, leaving only request order and the delivery of responses as sources of timing variability ๐.
Ensuring that processing time is not dependent on the value of a secret is the best defense against any form of timing attack. ๐
11. IANA Considerations ๐
This revision of HTTP/2 marks the HTTP2-Settings header field and the h2c upgrade token, both defined in [RFC7540], as obsolete ๐๏ธ.
Section 11 of [RFC7540] registered the h2 and h2c ALPN identifiers along with the PRI HTTP method. RFC 7540 also established a registry for frame types, settings, and error codes. These registrations and registries apply to HTTP/2, but are not redefined in this document ๐.
IANA has updated references to RFC 7540 in the following registries to refer to this document: "TLS Application-Layer Protocol Negotiation (ALPN) Protocol IDs", "HTTP/2 Frame Type", "HTTP/2 Settings", "HTTP/2 Error Code", and "HTTP Method Registry". The registration of the PRI method has been updated to refer to Section 3.4; all other section numbers have not changed ๐.
IANA has changed the policy on those portions of the "HTTP/2 Frame Type" and "HTTP/2 Settings" registries that were reserved for Experimental Use in RFC 7540. These portions of the registries shall operate on the same policy as the remainder of each registry ๐ง.
11.1. HTTP2-Settings Header Field Registration ๐ท๏ธ
This section marks the HTTP2-Settings header field registered by Section 11.5 of [RFC7540] in the "Hypertext Transfer Protocol (HTTP) Field Name Registry" as obsolete. This capability has been removed: see Section 3.1. The registration is updated to include the details as required by Section 18.4 of [HTTP]:
Field Name: HTTP2-Settings
Status: obsoleted
Reference: Section 3.2.1 of [RFC7540]
Comments: Obsolete; see Section 11.1 of this document.
11.2. The h2c Upgrade Token ๐ฆ
This section records the h2c upgrade token registered by Section 11.8 of [RFC7540] in the "Hypertext Transfer Protocol (HTTP) Upgrade Token Registry" as obsolete. This capability has been removed: see Section 3.1. The registration is updated as follows:
Value: h2c
Description: (OBSOLETE) Hypertext Transfer Protocol version 2
(HTTP/2)
Expected Version Tokens: None
Reference: Section 3.1 of this document
12. References ๐
- References
12.1. Normative References
Okay so this is the references bit, bestie, and it's where the whole spec shows its receipts ๐งพ๐ Every single one has to come along for the ride, no cap.
[CACHING] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., "HTTP Caching", STD 98, RFC 9111, DOI 10.17487/RFC9111, June 2022, <https://www.rfc-editor.org/info/rfc9111>.
[COMPRESSION] Peon, R. and H. Ruellan, "HPACK: Header Compression for HTTP/2", RFC 7541, DOI 10.17487/RFC7541, May 2015, <https://www.rfc-editor.org/info/rfc7541>.
[COOKIE] Barth, A., "HTTP State Management Mechanism", RFC 6265, DOI 10.17487/RFC6265, April 2011, <https://www.rfc-editor.org/info/rfc6265>.
[HTTP] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., "HTTP Semantics", STD 97, RFC 9110, DOI 10.17487/RFC9110, June 2022, <https://www.rfc-editor.org/info/rfc9110>.
[QUIC] Iyengar, J., Ed. and M. Thomson, Ed., "QUIC: A UDP-Based Multiplexed and Secure Transport", RFC 9000, DOI 10.17487/RFC9000, May 2021, <https://www.rfc-editor.org/info/rfc9000>.
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, <https://www.rfc-editor.org/info/rfc2119>.
[RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform Resource Identifier (URI): Generic Syntax", STD 66, RFC 3986, DOI 10.17487/RFC3986, January 2005, <https://www.rfc-editor.org/info/rfc3986>.
[RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017, <https://www.rfc-editor.org/info/rfc8174>.
[RFC8422] Nir, Y., Josefsson, S., and M. Pegourie-Gonnard, "Elliptic Curve Cryptography (ECC) Cipher Suites for Transport Layer Security (TLS) Versions 1.2 and Earlier", RFC 8422, DOI 10.17487/RFC8422, August 2018, <https://www.rfc-editor.org/info/rfc8422>.
[RFC8470] Thomson, M., Nottingham, M., and W. Tarreau, "Using Early Data in HTTP", RFC 8470, DOI 10.17487/RFC8470, September 2018, <https://www.rfc-editor.org/info/rfc8470>.
[TCP] Postel, J., "Transmission Control Protocol", STD 7, RFC 793, DOI 10.17487/RFC0793, September 1981, <https://www.rfc-editor.org/info/rfc793>.
[TLS-ALPN] Friedl, S., Popov, A., Langley, A., and E. Stephan, "Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension", RFC 7301, DOI 10.17487/RFC7301, July 2014, <https://www.rfc-editor.org/info/rfc7301>.
[TLS-ECDHE] Rescorla, E., "TLS Elliptic Curve Cipher Suites with SHA- 256/384 and AES Galois Counter Mode (GCM)", RFC 5289, DOI 10.17487/RFC5289, August 2008, <https://www.rfc-editor.org/info/rfc5289>.
[TLS-EXT] Eastlake 3rd, D., "Transport Layer Security (TLS) Extensions: Extension Definitions", RFC 6066, DOI 10.17487/RFC6066, January 2011, <https://www.rfc-editor.org/info/rfc6066>.
[TLS12] Dierks, T. and E. Rescorla, "The Transport Layer Security (TLS) Protocol Version 1.2", RFC 5246, DOI 10.17487/RFC5246, August 2008, <https://www.rfc-editor.org/info/rfc5246>.
[TLS13] Rescorla, E., "The Transport Layer Security (TLS) Protocol Version 1.3", RFC 8446, DOI 10.17487/RFC8446, August 2018, <https://www.rfc-editor.org/info/rfc8446>.
[TLSBCP] Sheffer, Y., Holz, R., and P. Saint-Andre, "Recommendations for Secure Use of Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS)", BCP 195, RFC 7525, DOI 10.17487/RFC7525, May 2015, <https://www.rfc-editor.org/info/rfc7525>.
12.2. Informative References
Now the informative ones, which are the extra-credit reading list for the nerdy ones ๐ค๐
[ALT-SVC] Nottingham, M., McManus, P., and J. Reschke, "HTTP Alternative Services", RFC 7838, DOI 10.17487/RFC7838, April 2016, <https://www.rfc-editor.org/info/rfc7838>.
[BREACH] Gluck, Y., Harris, N., and A. Prado, "BREACH: Reviving the CRIME Attack", 12 July 2013, <https://breachattack.com/resources/ BREACH%20-%20SSL,%20gone%20in%2030%20seconds.pdf>.
[DNS-TERMS] Hoffman, P., Sullivan, A., and K. Fujiwara, "DNS Terminology", BCP 219, RFC 8499, DOI 10.17487/RFC8499, January 2019, <https://www.rfc-editor.org/info/rfc8499>.
[HTTP-PRIORITY] Oku, K. and L. Pardue, "Extensible Prioritization Scheme for HTTP", RFC 9218, DOI 10.17487/RFC9218, June 2022, <https://www.rfc-editor.org/info/rfc9218>.
[HTTP/1.1] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., "HTTP/1.1", STD 99, RFC 9112, DOI 10.17487/RFC9112, June 2022, <https://www.rfc-editor.org/info/rfc9112>.
[NFLX-2019-002] Netflix, "HTTP/2 Denial of Service Advisory", 13 August 2019, <https://github.com/Netflix/security- bulletins/blob/master/advisories/third-party/2019-002.md>.
[PRIVACY] Cooper, A., Tschofenig, H., Aboba, B., Peterson, J., Morris, J., Hansen, M., and R. Smith, "Privacy Considerations for Internet Protocols", RFC 6973, DOI 10.17487/RFC6973, July 2013, <https://www.rfc-editor.org/info/rfc6973>.
[RFC1122] Braden, R., Ed., "Requirements for Internet Hosts - Communication Layers", STD 3, RFC 1122, DOI 10.17487/RFC1122, October 1989, <https://www.rfc-editor.org/info/rfc1122>.
[RFC3749] Hollenbeck, S., "Transport Layer Security Protocol Compression Methods", RFC 3749, DOI 10.17487/RFC3749, May 2004, <https://www.rfc-editor.org/info/rfc3749>.
[RFC6125] Saint-Andre, P. and J. Hodges, "Representation and Verification of Domain-Based Application Service Identity within Internet Public Key Infrastructure Using X.509 (PKIX) Certificates in the Context of Transport Layer Security (TLS)", RFC 6125, DOI 10.17487/RFC6125, March 2011, <https://www.rfc-editor.org/info/rfc6125>.
[RFC6585] Nottingham, M. and R. Fielding, "Additional HTTP Status Codes", RFC 6585, DOI 10.17487/RFC6585, April 2012, <https://www.rfc-editor.org/info/rfc6585>.
[RFC7323] Borman, D., Braden, B., Jacobson, V., and R. Scheffenegger, Ed., "TCP Extensions for High Performance", RFC 7323, DOI 10.17487/RFC7323, September 2014, <https://www.rfc-editor.org/info/rfc7323>.
[RFC7540] Belshe, M., Peon, R., and M. Thomson, Ed., "Hypertext Transfer Protocol Version 2 (HTTP/2)", RFC 7540, DOI 10.17487/RFC7540, May 2015, <https://www.rfc-editor.org/info/rfc7540>.
[RFC8441] McManus, P., "Bootstrapping WebSockets with HTTP/2", RFC 8441, DOI 10.17487/RFC8441, September 2018, <https://www.rfc-editor.org/info/rfc8441>.
[RFC8740] Benjamin, D., "Using TLS 1.3 with HTTP/2", RFC 8740, DOI 10.17487/RFC8740, February 2020, <https://www.rfc-editor.org/info/rfc8740>.
[TALKING] Huang, L., Chen, E., Barth, A., Rescorla, E., and C. Jackson, "Talking to Yourself for Fun and Profit", 2011, <https://www.adambarth.com/papers/2011/huang-chen-barth- rescorla-jackson.pdf>.
Appendix A. Prohibited TLS 1.2 Cipher Suites ๐ฆ
okay bestie, so an HTTP/2 implementation MAY treat the negotiation of any of these TLS 1.2 cipher suites as a connection error (Section 5.4.1) of type INADEQUATE_SECURITY ๐ it's the "absolutely not, we're not doing this" list ๐
โโ๏ธ
TLS_NULL_WITH_NULL_NULLTLS_RSA_WITH_NULL_MD5TLS_RSA_WITH_NULL_SHATLS_RSA_EXPORT_WITH_RC4_40_MD5TLS_RSA_WITH_RC4_128_MD5TLS_RSA_WITH_RC4_128_SHATLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5TLS_RSA_WITH_IDEA_CBC_SHATLS_RSA_EXPORT_WITH_DES40_CBC_SHATLS_RSA_WITH_DES_CBC_SHATLS_RSA_WITH_3DES_EDE_CBC_SHATLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHATLS_DH_DSS_WITH_DES_CBC_SHATLS_DH_DSS_WITH_3DES_EDE_CBC_SHATLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHATLS_DH_RSA_WITH_DES_CBC_SHATLS_DH_RSA_WITH_3DES_EDE_CBC_SHATLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHATLS_DHE_DSS_WITH_DES_CBC_SHATLS_DHE_DSS_WITH_3DES_EDE_CBC_SHATLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHATLS_DHE_RSA_WITH_DES_CBC_SHATLS_DHE_RSA_WITH_3DES_EDE_CBC_SHATLS_DH_anon_EXPORT_WITH_RC4_40_MD5TLS_DH_anon_WITH_RC4_128_MD5TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHATLS_DH_anon_WITH_DES_CBC_SHATLS_DH_anon_WITH_3DES_EDE_CBC_SHATLS_KRB5_WITH_DES_CBC_SHATLS_KRB5_WITH_3DES_EDE_CBC_SHATLS_KRB5_WITH_RC4_128_SHATLS_KRB5_WITH_IDEA_CBC_SHATLS_KRB5_WITH_DES_CBC_MD5TLS_KRB5_WITH_3DES_EDE_CBC_MD5TLS_KRB5_WITH_RC4_128_MD5TLS_KRB5_WITH_IDEA_CBC_MD5TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHATLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHATLS_KRB5_EXPORT_WITH_RC4_40_SHATLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5TLS_KRB5_EXPORT_WITH_RC4_40_MD5TLS_PSK_WITH_NULL_SHATLS_DHE_PSK_WITH_NULL_SHATLS_RSA_PSK_WITH_NULL_SHATLS_RSA_WITH_AES_128_CBC_SHATLS_DH_DSS_WITH_AES_128_CBC_SHATLS_DH_RSA_WITH_AES_128_CBC_SHATLS_DHE_DSS_WITH_AES_128_CBC_SHATLS_DHE_RSA_WITH_AES_128_CBC_SHATLS_DH_anon_WITH_AES_128_CBC_SHATLS_RSA_WITH_AES_256_CBC_SHATLS_DH_DSS_WITH_AES_256_CBC_SHATLS_DH_RSA_WITH_AES_256_CBC_SHATLS_DHE_DSS_WITH_AES_256_CBC_SHATLS_DHE_RSA_WITH_AES_256_CBC_SHATLS_DH_anon_WITH_AES_256_CBC_SHATLS_RSA_WITH_NULL_SHA256TLS_RSA_WITH_AES_128_CBC_SHA256TLS_RSA_WITH_AES_256_CBC_SHA256TLS_DH_DSS_WITH_AES_128_CBC_SHA256TLS_DH_RSA_WITH_AES_128_CBC_SHA256TLS_DHE_DSS_WITH_AES_128_CBC_SHA256TLS_RSA_WITH_CAMELLIA_128_CBC_SHATLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHATLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHATLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHATLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHATLS_DH_anon_WITH_CAMELLIA_128_CBC_SHATLS_DHE_RSA_WITH_AES_128_CBC_SHA256TLS_DH_DSS_WITH_AES_256_CBC_SHA256TLS_DH_RSA_WITH_AES_256_CBC_SHA256TLS_DHE_DSS_WITH_AES_256_CBC_SHA256TLS_DHE_RSA_WITH_AES_256_CBC_SHA256TLS_DH_anon_WITH_AES_128_CBC_SHA256TLS_DH_anon_WITH_AES_256_CBC_SHA256TLS_RSA_WITH_CAMELLIA_256_CBC_SHATLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHATLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHATLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHATLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHATLS_DH_anon_WITH_CAMELLIA_256_CBC_SHATLS_PSK_WITH_RC4_128_SHATLS_PSK_WITH_3DES_EDE_CBC_SHATLS_PSK_WITH_AES_128_CBC_SHATLS_PSK_WITH_AES_256_CBC_SHATLS_DHE_PSK_WITH_RC4_128_SHATLS_DHE_PSK_WITH_3DES_EDE_CBC_SHATLS_DHE_PSK_WITH_AES_128_CBC_SHATLS_DHE_PSK_WITH_AES_256_CBC_SHATLS_RSA_PSK_WITH_RC4_128_SHATLS_RSA_PSK_WITH_3DES_EDE_CBC_SHATLS_RSA_PSK_WITH_AES_128_CBC_SHATLS_RSA_PSK_WITH_AES_256_CBC_SHATLS_RSA_WITH_SEED_CBC_SHATLS_DH_DSS_WITH_SEED_CBC_SHATLS_DH_RSA_WITH_SEED_CBC_SHATLS_DHE_DSS_WITH_SEED_CBC_SHATLS_DHE_RSA_WITH_SEED_CBC_SHATLS_DH_anon_WITH_SEED_CBC_SHATLS_RSA_WITH_AES_128_GCM_SHA256TLS_RSA_WITH_AES_256_GCM_SHA384TLS_DH_RSA_WITH_AES_128_GCM_SHA256TLS_DH_RSA_WITH_AES_256_GCM_SHA384TLS_DH_DSS_WITH_AES_128_GCM_SHA256TLS_DH_DSS_WITH_AES_256_GCM_SHA384TLS_DH_anon_WITH_AES_128_GCM_SHA256TLS_DH_anon_WITH_AES_256_GCM_SHA384TLS_PSK_WITH_AES_128_GCM_SHA256TLS_PSK_WITH_AES_256_GCM_SHA384TLS_RSA_PSK_WITH_AES_128_GCM_SHA256TLS_RSA_PSK_WITH_AES_256_GCM_SHA384TLS_PSK_WITH_AES_128_CBC_SHA256TLS_PSK_WITH_AES_256_CBC_SHA384TLS_PSK_WITH_NULL_SHA256TLS_PSK_WITH_NULL_SHA384TLS_DHE_PSK_WITH_AES_128_CBC_SHA256TLS_DHE_PSK_WITH_AES_256_CBC_SHA384TLS_DHE_PSK_WITH_NULL_SHA256TLS_DHE_PSK_WITH_NULL_SHA384TLS_RSA_PSK_WITH_AES_128_CBC_SHA256TLS_RSA_PSK_WITH_AES_256_CBC_SHA384TLS_RSA_PSK_WITH_NULL_SHA256TLS_RSA_PSK_WITH_NULL_SHA384TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256TLS_EMPTY_RENEGOTIATION_INFO_SCSVTLS_ECDH_ECDSA_WITH_NULL_SHATLS_ECDH_ECDSA_WITH_RC4_128_SHATLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHATLS_ECDH_ECDSA_WITH_AES_128_CBC_SHATLS_ECDH_ECDSA_WITH_AES_256_CBC_SHATLS_ECDHE_ECDSA_WITH_NULL_SHATLS_ECDHE_ECDSA_WITH_RC4_128_SHATLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHATLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHATLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHATLS_ECDH_RSA_WITH_NULL_SHATLS_ECDH_RSA_WITH_RC4_128_SHATLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHATLS_ECDH_RSA_WITH_AES_128_CBC_SHATLS_ECDH_RSA_WITH_AES_256_CBC_SHATLS_ECDHE_RSA_WITH_NULL_SHATLS_ECDHE_RSA_WITH_RC4_128_SHATLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHATLS_ECDHE_RSA_WITH_AES_128_CBC_SHATLS_ECDHE_RSA_WITH_AES_256_CBC_SHATLS_ECDH_anon_WITH_NULL_SHATLS_ECDH_anon_WITH_RC4_128_SHATLS_ECDH_anon_WITH_3DES_EDE_CBC_SHATLS_ECDH_anon_WITH_AES_128_CBC_SHATLS_ECDH_anon_WITH_AES_256_CBC_SHATLS_SRP_SHA_WITH_3DES_EDE_CBC_SHATLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHATLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHATLS_SRP_SHA_WITH_AES_128_CBC_SHATLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHATLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHATLS_SRP_SHA_WITH_AES_256_CBC_SHATLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHATLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHATLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384TLS_ECDHE_PSK_WITH_RC4_128_SHATLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHATLS_ECDHE_PSK_WITH_AES_128_CBC_SHATLS_ECDHE_PSK_WITH_AES_256_CBC_SHATLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384TLS_ECDHE_PSK_WITH_NULL_SHATLS_ECDHE_PSK_WITH_NULL_SHA256TLS_ECDHE_PSK_WITH_NULL_SHA384TLS_RSA_WITH_ARIA_128_CBC_SHA256TLS_RSA_WITH_ARIA_256_CBC_SHA384TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384TLS_DH_anon_WITH_ARIA_128_CBC_SHA256TLS_DH_anon_WITH_ARIA_256_CBC_SHA384TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384TLS_RSA_WITH_ARIA_128_GCM_SHA256TLS_RSA_WITH_ARIA_256_GCM_SHA384TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384TLS_DH_anon_WITH_ARIA_128_GCM_SHA256TLS_DH_anon_WITH_ARIA_256_GCM_SHA384TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384TLS_PSK_WITH_ARIA_128_CBC_SHA256TLS_PSK_WITH_ARIA_256_CBC_SHA384TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384TLS_PSK_WITH_ARIA_128_GCM_SHA256TLS_PSK_WITH_ARIA_256_GCM_SHA384TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384TLS_RSA_WITH_AES_128_CCMTLS_RSA_WITH_AES_256_CCMTLS_RSA_WITH_AES_128_CCM_8TLS_RSA_WITH_AES_256_CCM_8TLS_PSK_WITH_AES_128_CCMTLS_PSK_WITH_AES_256_CCMTLS_PSK_WITH_AES_128_CCM_8TLS_PSK_WITH_AES_256_CCM_8
Heads up, this list was put together from the registered TLS cipher suites when [RFC7540] was being developed. It includes the suites that don't offer an ephemeral key exchange, plus the ones built on TLS null, stream, or block cipher types (as defined in Section 6.2.3 of [TLS12]). New suites with those properties could be defined later, and those would not be explicitly prohibited. ๐
For more details, see Section 9.2.2. ๐
Appendix B. Changes from RFC 7540 โจ
okay bestie, this is the changelog ๐ here's everything that changed in this revision:
- Use of TLS 1.3 was defined based on [RFC8740], which this document obsoletes. ๐
- The priority scheme defined in RFC 7540 is deprecated. ๐ โโ๏ธ The format of the PRIORITY frame and the priority fields in the HEADERS frame stayed, plus the rules for when PRIORITY frames can be sent and received. But the semantics of those fields are only described in RFC 7540 now. The priority signaling scheme from RFC 7540 was not successful, so using the simpler signaling in [HTTP-PRIORITY] is recommended. ๐ธ
- The HTTP/1.1 Upgrade mechanism is deprecated and no longer specified in this document. ๐ฅ It was never widely deployed, since plaintext HTTP/2 users chose the prior-knowledge implementation instead. ๐ฆ
- Validation for field names and values has been narrowed. โ๏ธ The validation that is mandatory for intermediaries is precisely defined now, and error reporting for requests was amended to encourage sending 400-series status codes. ๐
- The ranges of codepoints for settings and frame types that were reserved for Experimental Use are now available for general use. ๐
- Connection-specific header fields are prohibited, and they're now more precisely and comprehensively identified. ๐
- Host and ":authority" are no longer permitted to disagree. ๐ โโ๏ธ๐
- Rules for sending Dynamic Table Size Update instructions after changes in settings have been clarified in Section 4.3.1. ๐ง
Editorial changes are also included. โจ Changes to terminology and document structure respond to updates to core HTTP semantics [HTTP]. Those documents now include some concepts that were first defined in RFC 7540, like the 421 status code or connection coalescing. ๐
Acknowledgments ๐ซถ
Credit for non-trivial input to this document is owed to a huge number of people who have contributed to the HTTP Working Group over the years. ๐ญ [RFC7540] has a more extensive list of people who deserve acknowledgment for their contributions. ๐
Contributors ๐
Mike Belshe and Roberto Peon authored the text that this document is based on. ๐
Authors' Addresses ๐
Martin Thomson (editor)
Mozilla
Australia
Email: mt@lowentropy.net
Cory Benfield (editor)
Apple Inc.
Email: cbenfield@apple.com
๐ฆ where this page comes from
- ietf: rfc9113 2022-06
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms