Skip to main content

4. Expressing HTTP Semantics in HTTP/3

4.1. HTTP Message Framing

A client sends an HTTP request on a request stream, which is a client-initiated bidirectional QUIC stream; see Section 6.1. A client MUST send only a single request on a given stream. A server sends zero or more interim HTTP responses on the same stream as the request, followed by a single final HTTP response, as detailed below. See Section 15 of [HTTP] for a description of interim and final HTTP responses.

Pushed responses are sent on a server-initiated unidirectional QUIC stream; see Section 6.2.2. A server sends zero or more interim HTTP responses, followed by a single final HTTP response, in the same manner as a standard response. Push is described in more detail in Section 4.6.

On a given stream, receipt of multiple requests or receipt of an additional HTTP response following a final HTTP response MUST be treated as malformed.

An HTTP message (request or response) consists of:

  1. the header section, including message control data, sent as a single HEADERS frame,

  2. optionally, the content, if present, sent as a series of DATA frames, and

  3. optionally, the trailer section, if present, sent as a single HEADERS frame.

Header and trailer sections are described in Sections 6.3 and 6.5 of [HTTP]; the content is described in Section 6.4 of [HTTP].

Receipt of an invalid sequence of frames MUST be treated as a connection error of type H3_FRAME_UNEXPECTED. In particular, a DATA frame before any HEADERS frame, or a HEADERS or DATA frame after the trailing HEADERS frame, is considered invalid. Other frame types, especially unknown frame types, might be permitted subject to their own rules; see Section 9.

A server MAY send one or more PUSH_PROMISE frames before, after, or interleaved with the frames of a response message. These PUSH_PROMISE frames are not part of the response; see Section 4.6 for more details. PUSH_PROMISE frames are not permitted on push streams; a pushed response that includes PUSH_PROMISE frames MUST be treated as a connection error of type H3_FRAME_UNEXPECTED.

Frames of unknown types (Section 9), including reserved frames (Section 7.2.8) MAY be sent on a request or push stream before, after, or interleaved with other frames described in this section.

The HEADERS and PUSH_PROMISE frames might reference updates to the QPACK dynamic table. While these updates are not directly part of the message exchange, they must be received and processed before the message can be consumed. See Section 4.2 for more details.

Transfer codings (see Section 7 of [HTTP/1.1]) are not defined for HTTP/3; the Transfer-Encoding header field MUST NOT be used.

A response MAY consist of multiple messages when and only when one or more interim responses (1xx; see Section 15.2 of [HTTP]) precede a final response to the same request. Interim responses do not contain content or trailer sections.

An HTTP request/response exchange fully consumes a client-initiated bidirectional QUIC stream. After sending a request, a client MUST close the stream for sending. Unless using the CONNECT method (see Section 4.4), clients MUST NOT make stream closure dependent on receiving a response to their request. After sending a final response, the server MUST close the stream for sending. At this point, the QUIC stream is fully closed.

When a stream is closed, this indicates the end of the final HTTP message. Because some messages are large or unbounded, endpoints SHOULD begin processing partial HTTP messages once enough of the message has been received to make progress. If a client-initiated stream terminates without enough of the HTTP message to provide a complete response, the server SHOULD abort its response stream with the error code H3_REQUEST_INCOMPLETE.

A server can send a complete response prior to the client sending an entire request if the response does not depend on any portion of the request that has not been sent and received. When the server does not need to receive the remainder of the request, it MAY abort reading the request stream, send a complete response, and cleanly close the sending part of the stream. The error code H3_NO_ERROR SHOULD be used when requesting that the client stop sending on the request stream. Clients MUST NOT discard complete responses as a result of having their request terminated abruptly, though clients can always discard responses at their discretion for other reasons. If the server sends a partial or complete response but does not abort reading the request, clients SHOULD continue sending the content of the request and close the stream normally.

4.1.1. Request Cancellation and Rejection

Once a request stream has been opened, the request MAY be cancelled by either endpoint. Clients cancel requests if the response is no longer of interest; servers cancel requests if they are unable to or choose not to respond. When possible, it is RECOMMENDED that servers send an HTTP response with an appropriate status code rather than cancelling a request it has already begun processing.

Implementations SHOULD cancel requests by abruptly terminating any directions of a stream that are still open. To do so, an implementation resets the sending parts of streams and aborts reading on the receiving parts of streams; see Section 2.4 of [QUIC-TRANSPORT].

When the server cancels a request without performing any application processing, the request is considered "rejected". The server SHOULD abort its response stream with the error code H3_REQUEST_REJECTED. 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. The client can treat requests rejected by the server as though they had never been sent at all, thereby allowing them to be retried later.

Servers MUST NOT use the H3_REQUEST_REJECTED error code for requests that were partially or fully processed. When a server abandons a response after partial processing, it SHOULD abort its response stream with the error code H3_REQUEST_CANCELLED.

Client SHOULD use the error code H3_REQUEST_CANCELLED to cancel requests. Upon receipt of this error code, a server MAY abruptly terminate the response using the error code H3_REQUEST_REJECTED if no processing was performed. Clients MUST NOT use the H3_REQUEST_REJECTED error code, except when a server has requested closure of the request stream with this error code.

If a stream is cancelled after receiving a complete response, the client MAY ignore the cancellation and use the response. However, if a stream is cancelled after receiving a partial response, the response SHOULD NOT be used. Only idempotent actions such as GET, PUT, or DELETE can be safely retried; a client SHOULD NOT automatically retry a request with a non-idempotent method unless it has some means to know that the request semantics are idempotent independent of the method or some means to detect that the original request was never applied. See Section 9.2.2 of [HTTP] for more details.

4.1.2. Malformed Requests and Responses

A malformed request or response is one that is an otherwise valid sequence of frames but is invalid due to:

  • the presence of prohibited fields or pseudo-header fields,

  • the absence of mandatory pseudo-header fields,

  • invalid values for pseudo-header fields,

  • pseudo-header fields after fields,

  • an invalid sequence of HTTP messages,

  • the inclusion of uppercase field names, or

  • the inclusion of invalid characters in field names or values.

A request or response that is defined as having content when it contains a Content-Length header field (Section 8.6 of [HTTP]) is malformed if the value of the Content-Length header field does not equal the sum of the DATA frame lengths received. A response that is defined as never having content, even when a Content-Length is present, can 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 of type H3_MESSAGE_ERROR.

For malformed requests, a server MAY send an HTTP response indicating the error prior to closing or resetting the stream. Clients MUST NOT accept a malformed response. Note that 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.

4.2. HTTP Fields

HTTP messages carry metadata as a series of key-value pairs called "HTTP fields"; see Sections 6.3 and 6.5 of [HTTP]. For a listing of registered HTTP fields, see the "Hypertext Transfer Protocol (HTTP) Field Name Registry" maintained at \https://www.iana.org/assignments/http-fields/\``. Like HTTP/2, HTTP/3 has additional considerations related to the use of characters in field names, the Connection header field, and pseudo-header fields.

Field names are strings containing a subset of ASCII characters. Properties of HTTP field names and values are discussed in more detail in Section 5.1 of [HTTP]. Characters in field names MUST be converted to lowercase prior to their encoding. A request or response containing uppercase characters in field names MUST be treated as malformed.

HTTP/3 does not use the Connection header field to indicate connection-specific fields; in this protocol, connection-specific metadata is conveyed by other means. An endpoint MUST NOT generate an HTTP/3 field section containing connection-specific fields; any message containing connection-specific fields MUST be treated as malformed.

The only exception to this is the TE header field, which MAY be present in an HTTP/3 request header; when it is, it MUST NOT contain any value other than "trailers".

An intermediary transforming an HTTP/1.x message to HTTP/3 MUST remove connection-specific header fields as discussed in Section 7.6.1 of [HTTP], or their messages will be treated by other HTTP/3 endpoints as malformed.

4.2.1. Field Compression

[QPACK] describes a variation of HPACK that gives an encoder some control over how much head-of-line blocking can be caused by compression. This allows an encoder to balance compression efficiency with latency. HTTP/3 uses QPACK to compress header and trailer sections, including the control data present in the header section.

To allow for better compression efficiency, the Cookie header field ([COOKIES]) MAY be split into separate field lines, each with one or more cookie-pairs, before compression. If a decompressed field section contains multiple cookie field lines, these MUST be concatenated into a single byte string using the two-byte delimiter of "; " (ASCII 0x3b, 0x20) before being passed into a context other than HTTP/2 or HTTP/3, such as an HTTP/1.1 connection, or a generic HTTP server application.

4.2.2. Header Size Constraints

An HTTP/3 implementation MAY impose a limit on the maximum size of the message header it will accept on an individual HTTP message. A server that receives a larger header section than it is willing to handle can send an HTTP 431 (Request Header Fields Too Large) status code ([RFC6585]). A client can discard responses that it cannot process. The size of a field list is calculated based on the uncompressed size of fields, including the length of the name and value in bytes plus an overhead of 32 bytes for each field.

If an implementation wishes to advise its peer of this limit, it can be conveyed as a number of bytes in the SETTINGS_MAX_FIELD_SECTION_SIZE parameter. An implementation that has received this parameter SHOULD NOT send an HTTP message header that exceeds the indicated size, as the peer will likely refuse to process it. However, an HTTP message can traverse one or more intermediaries before reaching the origin server; see Section 3.7 of [HTTP]. Because this limit is applied separately by each implementation that processes the message, messages below this limit are not guaranteed to be accepted.

4.3. HTTP Control Data

Like HTTP/2, HTTP/3 employs a series of pseudo-header fields, where the field name begins with the : character (ASCII 0x3a). These pseudo-header fields convey message control data; see Section 6.2 of [HTTP].

Pseudo-header fields are not HTTP fields. Endpoints MUST NOT generate pseudo-header fields other than those defined in this document. However, an extension could negotiate a modification of this restriction; see Section 9.

Pseudo-header fields are only valid in the context in which they are defined. Pseudo-header fields defined for requests MUST NOT appear in responses; pseudo-header fields defined for responses MUST NOT appear in requests. Pseudo-header fields MUST NOT appear in trailer sections. Endpoints MUST treat a request or response that contains undefined or invalid pseudo-header fields as malformed.

All pseudo-header fields MUST appear in the header section before regular header fields. Any request or response that contains a pseudo-header field that appears in a header section after a regular header field MUST be treated as malformed.

4.3.1. Request Pseudo-Header Fields

The following pseudo-header fields are defined for requests:

":method": Contains the HTTP method (Section 9 of [HTTP])

":scheme": Contains the scheme portion of the target URI (Section 3.1 of [URI]).

The :scheme pseudo-header is not restricted to URIs with scheme "http" and "https". A proxy or gateway can translate requests for non-HTTP schemes, enabling the use of HTTP to interact with non-HTTP services.

See Section 3.1.2 for guidance on using a scheme other than "https".

":authority": Contains the authority portion of the target URI (Section 3.2 of [URI]). The authority MUST NOT include the deprecated userinfo subcomponent for URIs of scheme "http" or "https".

To ensure that the HTTP/1.1 request line can be reproduced accurately, this pseudo-header field MUST be omitted when translating from an HTTP/1.1 request that has a request target in a method-specific form; see Section 7.1 of [HTTP]. Clients that generate HTTP/3 requests directly SHOULD use the :authority pseudo-header field instead of the Host header field. An intermediary that converts an HTTP/3 request to HTTP/1.1 MUST create a Host field if one is not present in a request by copying the value of the :authority pseudo-header field.

":path": Contains the path and query parts of the target URI (the "path-absolute" production and optionally a ? character (ASCII 0x3f) followed by the "query" production; see Sections 3.3 and 3.4 of [URI].

This pseudo-header field MUST NOT be empty for "http" or "https" URIs; "http" or "https" URIs that do not contain a path component MUST include a value of / (ASCII 0x2f). An OPTIONS request that does not include a path component includes the value * (ASCII 0x2a) for the :path pseudo-header field; see Section 7.1 of [HTTP].

All HTTP/3 requests MUST include exactly one value for the :method, :scheme, and :path pseudo-header fields, unless the request is a CONNECT request; see Section 4.4.

If the :scheme pseudo-header field identifies a scheme that has a mandatory authority component (including "http" and "https"), the request MUST contain either an :authority pseudo-header field or a Host header field. If these fields are present, they MUST NOT be empty. If both fields are present, they MUST contain the same value. If the scheme does not have a mandatory authority component and none is provided in the request target, the request MUST NOT contain the :authority pseudo-header or Host header fields.

An HTTP request that omits mandatory pseudo-header fields or contains invalid values for those pseudo-header fields is malformed.

HTTP/3 does not define a way to carry the version identifier that is included in the HTTP/1.1 request line. HTTP/3 requests implicitly have a protocol version of "3.0".

4.3.2. Response Pseudo-Header Fields

For responses, a single ":status" pseudo-header field is defined that carries the HTTP status code; see Section 15 of [HTTP]. This pseudo-header field MUST be included in all responses; otherwise, the response is malformed (see Section 4.1.2).

HTTP/3 does not define a way to carry the version or reason phrase that is included in an HTTP/1.1 status line. HTTP/3 responses implicitly have a protocol version of "3.0".

4.4. The CONNECT Method

The CONNECT method requests that the recipient establish a tunnel to the destination origin server identified by the request-target; see Section 9.3.6 of [HTTP]. It is primarily used with HTTP proxies to establish a TLS session with an origin server for the purposes of interacting with "https" resources.

In HTTP/1.x, CONNECT is used to convert an entire HTTP connection into a tunnel to a remote host. In HTTP/2 and HTTP/3, the CONNECT method is used to establish a tunnel over a single stream.

A CONNECT request MUST be constructed as follows:

  • The :method pseudo-header field is set to "CONNECT"

  • The :scheme and :path pseudo-header fields are omitted

  • The :authority pseudo-header field contains the host and port to connect to (equivalent to the authority-form of the request-target of CONNECT requests; see Section 7.1 of [HTTP]).

The request stream remains open at the end of the request to carry the data to be transferred. A CONNECT request that does not conform to these restrictions is malformed.

A proxy that supports CONNECT establishes a TCP connection ([RFC0793]) to the server identified in the :authority pseudo-header field. Once this connection is successfully established, the proxy sends a HEADERS frame containing a 2xx series status code to the client, as defined in Section 15.3 of [HTTP].

All DATA frames on the stream correspond to data sent or received on the TCP connection. The payload of any DATA frame sent by the client is transmitted by the proxy to the TCP server; data received from the TCP server is packaged into DATA frames by the proxy. Note that the size and number of TCP segments is not guaranteed to map predictably to the size and number of HTTP DATA or QUIC STREAM frames.

Once the CONNECT method has completed, only DATA frames are permitted to be sent on the stream. Extension frames MAY be used if specifically permitted by the definition of the extension. Receipt of any other known frame type MUST be treated as a connection error of type H3_FRAME_UNEXPECTED.

The TCP connection can be closed by either peer. When the client ends the request stream (that is, the receive stream at the proxy enters the "Data Recvd" state), the proxy will set the FIN bit on its connection to the TCP server. When the proxy receives a packet with the FIN bit set, it will close the send stream that it sends to the client. TCP connections that remain half closed in a single direction are not invalid, but are often handled poorly by servers, so clients SHOULD NOT close a stream for sending while they still expect to receive data from the target of the CONNECT.

A TCP connection error is signaled by abruptly terminating the stream. A proxy treats any error in the TCP connection, which includes receiving a TCP segment with the RST bit set, as a stream error of type H3_CONNECT_ERROR.

Correspondingly, if a proxy detects an error with the stream or the QUIC connection, it MUST close the TCP connection. If the proxy detects that the client has reset the stream or aborted reading from the stream, it MUST close the TCP connection. If the stream is reset or reading is aborted by the client, a proxy SHOULD perform the same operation on the other direction in order to ensure that both directions of the stream are cancelled. In all these cases, if the underlying TCP implementation permits it, the proxy SHOULD send a TCP segment with the RST bit set.

Since CONNECT creates a tunnel to an arbitrary server, proxies that support CONNECT SHOULD restrict its use to a set of known ports or a list of safe request targets; see Section 9.3.6 of [HTTP] for more details.

4.5. HTTP Upgrade

HTTP/3 does not support the HTTP Upgrade mechanism (Section 7.8 of [HTTP]) or the 101 (Switching Protocols) informational status code (Section 15.2.2 of [HTTP]).

4.6. Server Push

Server push is an interaction mode that permits a server to push a request-response exchange to a client in anticipation of the client making the request. A client can disable server push by setting SETTINGS_ENABLE_PUSH to 0 in a SETTINGS frame. A server MUST NOT send a push to a client that has set SETTINGS_ENABLE_PUSH to 0; server behavior that violates this MUST be treated as a connection error of type H3_SETTINGS_ERROR.

Like HTTP/2, the server initiates a push by sending a PUSH_PROMISE frame (Section 7.2.5) on a client-initiated request stream. The push ID is used to identify a server push (see Section 4.6.1). The push ID is carried in the PUSH_PROMISE frame, which also includes request header section attributed to the server-generated request, as described in Section 15 of [HTTP].

The server sends the response from a push stream it initiates (Section 6.2.2). The delivery of the pushed response is identical to that of a response to a regular request. The response header section for the pushed response is carried in a HEADERS frame as described in Section 7.2.4. A server can cancel a promised push by sending a CANCEL_PUSH frame with the push ID on the push stream.

Clients control the number of pushes the server can promise using the MAX_PUSH_ID frame (Section 7.2.7). A server MUST NOT send a PUSH_PROMISE frame or a CANCEL_PUSH frame with a push ID that is greater than the maximum push ID the client has provided for the connection. Clients MUST treat an attempt to do so as a connection error of type H3_ID_ERROR.

Once a push stream has been opened or reserved by a PUSH_PROMISE frame, the push stream can be used as long as the client has not cancelled the push. Once a client receives a CANCEL_PUSH frame from the control stream or a stream termination from the push stream, the push is cancelled. If the push stream terminates without a CANCEL_PUSH, the push is still considered successfully completed.

Clients can abort a push by sending a CANCEL_PUSH frame. After the server receives it, the server MUST abort sending the push if the push is not yet complete. Clients can also abort a push by resetting the push stream. In both cases, the receiver can safely discard any push response status that has been received.

Once a request stream closes, implementations can choose to buffer only a reference to the push response or remove the reference to the push response entirely. If a push response is received with an associated request stream closed, this does not indicate a failure of the push.

Push streams are always referenced by a push ID. The recipient of a PUSH_PROMISE frame associates the push ID with a client-initiated stream, and a client receiving a HEADERS frame on a push stream matches the push ID with a received push.

4.6.1. Push IDs

Push IDs are 62-bit unsigned integers (see Section 16 of [QUIC-TRANSPORT]) used to identify a server push. Push IDs are unique for the lifetime of the connection.

The push ID space starts at zero and is a subset of the integer space; therefore, push IDs cannot appear in contexts that require a stream ID or a request ID. In particular, push IDs are not permitted to appear in GOAWAY frames (see Section 5.2).

Push IDs are used in a single PUSH_PROMISE frame (see Section 7.2.5) and a single push stream (see Section 4.6 and Section 6.2.2). These uses MUST reference the same promised push made by the server over the lifetime of the connection.

After sending a push response on a push stream, the push ID cannot be reused. If a client receives another push stream header or another PUSH_PROMISE on the same push ID from different streams, this MUST be treated as a connection error of type H3_ID_ERROR.