Skip to main content

8. Expressing HTTP Semantics in HTTP/2

HTTP/2 is intended to be as compatible as possible with current uses of HTTP. This means that, from the application perspective, the features of the protocol are largely unchanged. To achieve this, all request and response semantics are preserved through the mechanisms in HTTP/1.1 [HTTP], although the syntax of conveying those semantics has changed.

Thus, the specification and requirements of HTTP [HTTP], semantics and syntax, as well as extensions such as [COOKIES], apply to HTTP/2. The restrictions and additional requirements described in this section are limited to those necessary for successful implementation of HTTP/2, which relate to HTTP's wire format or which in the absence of additional definition in HTTP/2 would result in ambiguity.

8.1. HTTP Message Framing

An HTTP message (request or response) consists of:

  1. for a response only, zero or more HEADERS frames (each followed by zero or more CONTINUATION frames) containing the message headers of informational (1xx) HTTP responses (see Section 15 of [HTTP]), and/or

  2. one HEADERS frame (followed by zero or more CONTINUATION frames) containing the message headers (see Section 6.3 of [HTTP]), and

  3. zero or more DATA frames containing the message content (see Section 6.4 of [HTTP]), and

  4. optionally, one HEADERS frame (followed by zero or more CONTINUATION frames) containing the trailer-part field section (see Section 6.5 of [HTTP]).

The last frame in the sequence bears an END_STREAM flag, noting that a HEADERS frame bearing the END_STREAM flag can be followed by CONTINUATION frames that carry any remaining portion of the header block. Other frames (from any stream) MUST NOT occur between the HEADERS frame and any CONTINUATION frames that might follow.

HTTP/2 uses DATA frames to carry message content. The [CHUNKED] transfer coding (defined in Section 7.1 of [HTTP]) MUST NOT be used with HTTP/2.

Trailer fields are carried in a field section; see Section 8.2. There is no defined field section name for trailer fields because all trailer fields are encoded into the same field section in HTTP/2. There is no defined order between field lines; see Section 8.2.1. Finally, trailer fields do not include pseudo-header fields (see Section 8.3).

An HTTP request or response is malformed if it is one of the following:

  • It contains a field line with an invalid field name (see Section 5.1 of [HTTP]), a field line with an invalid field line value (see Section 5.5 of [HTTP]), or a pseudo-header field that is not permitted to appear in that context (see Section 8.3),

  • It omits a pseudo-header field that is required for that message type (see Section 8.3),

  • It contains uppercase characters in field line names, or

  • It contains an invalid Content-Length field value.

Error handling for streams is described in Section 8.1.1.

In addition to the mandatory parts of a message, PRIORITY information can be present in HEADERS frames. See Section 5.3 for more information.

Receipt of a HEADERS frame containing an invalid field block results in a stream error (see Section 5.4.2).

8.1.1. Malformed Messages

A malformed message is one that is an otherwise valid sequence of frames but is invalid due to the presence of prohibited fields, the absence of required fields, or invalid field values. A peer that detects a malformed request or response MUST respond with a stream error (see Section 5.4.2). For malformed requests, a server MAY send an HTTP response 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; they are deliberately strict because being permissive can expose implementations to these vulnerabilities.

8.2. HTTP Fields

HTTP field names and values are sequences of octets and are used with the same encoding as used in HTTP/1.x (see Section 5.1 of [HTTP]). However, field names MUST be converted to lowercase prior to their encoding in HTTP/2. A request or response containing uppercase field name characters MUST be treated as malformed (Section 8.1.1).

HTTP/2 does not use the Connection header field (see Section 7.6.1 of [HTTP]) to indicate connection-specific fields; in this protocol, connection-specific metadata is conveyed by other means. An endpoint MUST NOT generate an HTTP/2 message containing connection-specific fields; any message containing connection-specific fields MUST be treated as malformed (Section 8.1.1).

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".

This means that an intermediary transforming an HTTP/1.x message to HTTP/2 will need to remove any fields nominated by the Connection field, along with the Connection field itself. Such intermediaries SHOULD also remove other connection-specific fields, such as Keep-Alive, Proxy-Connection, Transfer-Encoding, and Upgrade, even if they are not nominated by the Connection field.

| Note: HTTP/2 purposefully does not support upgrade to another protocol. The | handshake methods described in Section 3 are believed sufficient | for negotiating the use of alternative protocols.

8.2.1. Field Validity

Field names are validated according to the rules in Section 5.1 of [HTTP], but converted to lowercase prior to representation in HTTP/2.

A field section compressed using [COMPRESSION] can contain whitespace as defined by the "optional whitespace" (OWS) production; whitespace is not part of the field; it MUST be removed during decompression or prior to passing the message to a non-HTTP/2 context, such as an HTTP/1.1 connection, or a generic HTTP server application.

The values of pseudo-header fields are specified in Section 8.3.

8.2.2. Connection-Specific Header Fields

HTTP/2 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 a message containing connection-specific fields; any message containing connection-specific fields MUST be treated as malformed (Section 8.1.1).

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

The Cookie header field [COOKIES] uses a semicolon (";") to delimit cookie-pairs (or "crumbs"). This header field doesn't follow the list rules (see Section 5.3 of [HTTP]) of comma separation, which can prevent cookie-pairs from being compressed efficiently. Because the Cookie header field can often contain a relatively large amount of data, this can significantly degrade compression efficiency.

To allow for better compression, 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

HTTP/2 uses special pseudo-header fields beginning with ':' character (ASCII 0x3a) to convey information about the request or response that, in HTTP/1.x, was carried in the message start-line. Pseudo-header fields are not HTTP fields. Endpoints MUST NOT generate pseudo-header fields other than those defined in this document.

Pseudo-header fields are only valid in the field section of a HEADERS and PUSH_PROMISE frame. However, a field section carrying trailer fields MUST NOT contain pseudo-header fields; a trailer-part field section containing pseudo-header fields MUST be treated as malformed (Section 8.1.1).

Pseudo-header fields start with a ':' character (ASCII 0x3a). Their names are field names that consist of just this prefix; the colon is not part of the field name. The colon is not part of the token set used for field names (see Section 5.1 of [HTTP]).

All pseudo-header fields MUST appear before all regular fields. Any request or response that contains a pseudo-header field that appears after a regular field MUST be treated as malformed (Section 8.1.1).

The following pseudo-header fields are defined:

8.3.1. Request Pseudo-Header Fields

The following pseudo-header fields are defined for HTTP/2 requests:

  • The :method pseudo-header field contains the HTTP method (Section 9 of [HTTP]).

  • The :scheme pseudo-header field contains the scheme portion of the target URI (Section 3.1 of [URI]).

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

    See Section 8.5 for the CONNECT method, which omits the :scheme pseudo-header field.

  • The :authority pseudo-header field contains the authority portion of the target URI (Section 3.2 of [URI]). The authority MUST NOT include the deprecated userinfo subcomponent for "http" or "https" schemed URIs.

    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 origin or asterisk form (see Section 3.2 of [HTTP]). Clients that generate HTTP/2 requests directly MUST use the :authority pseudo-header field instead of the Host field. An intermediary that converts an HTTP/2 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.

    A server SHOULD treat a request as malformed if it contains a request that has both the :authority pseudo-header field and the Host field, if the field values are not identical.

  • The :path pseudo-header field contains the path and query parts of the target URI (the absolute-path production and optionally a '?' character followed by the query production (see Sections 3.3 and 3.4 of [URI])). A request in asterisk form includes the value '*' for the :path pseudo-header field.

    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 '/', unless the request is made in asterisk form, in which case it MUST contain a value of '*'.

    See Section 8.5 for the CONNECT method, which omits the :path pseudo-header field.

All HTTP/2 requests MUST include exactly one valid value for the :method and :scheme pseudo-header fields, unless it is a CONNECT request (Section 8.5).

If the :scheme pseudo-header field identifies a scheme that uses an authority (see Section 3.2 of [URI]), then an HTTP/2 request that does not contain the request target MUST include either the :authority field or the Host field. If these fields are absent, the server receiving the request SHOULD respond with a 400 (Bad Request) status code (see Section 15.5.1 of [HTTP]).

HTTP/2 does not define a way to carry the version identifier that is included in the HTTP/1.1 request line.

8.3.2. Response Pseudo-Header Fields

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

HTTP/2 does not define a way to carry the version or reason phrase that is included in an HTTP/1.1 status line.

8.4. Server Push

HTTP/2 enables a server to pre-emptively send (or "push") responses to a client in association with a previous client-initiated request. This can be useful when the server knows the client will need to have those responses available in order to fully process the response to the original request.

A pushed response is always associated with an explicit request from the client. The server sends a PUSH_PROMISE frame on that request's stream. The PUSH_PROMISE frame includes a field section that contains a complete set of request fields that the server attributes to the request.

There is no limit to the number of responses that can be pushed by a server, aside from the available stream identifiers on a single connection. However, a client can limit the number of concurrently pushed streams by changing the SETTINGS_MAX_CONCURRENT_STREAMS setting. Setting this value to zero disables server push by preventing the server from creating the necessary streams. This does not prevent a server from sending PUSH_PROMISE frames; clients need to reset any unwanted promised streams.

8.4.1. Push Requests

Server push is semantically equivalent to a server responding to a request; however, in this case, that request is also sent by the server, as a PUSH_PROMISE frame.

The PUSH_PROMISE frame includes a field section that contains a complete set of request fields that the server attributes to the request. It is not possible to push a response to a request that includes a request body, except in the case where the HTTP/2 connection was initiated over TLS (see [TLS13]).

Pushed requests MUST be cacheable (see Section 9.2.3 of [HTTP]), MUST be safe (see Section 9.2.1 of [HTTP]), and MUST NOT include request content. Clients that receive a pushed request that is not cacheable, is not safe, or that includes request content MUST reset the promised stream with a stream error (Section 5.4.2) of type PROTOCOL_ERROR.

Pushed request fields and request pseudo-header fields MUST be authoritative (see Section 10.1). The server MUST include a value in the :authority pseudo-header field for which the server is authoritative (see Section 10.1). A client MUST treat a pushed request that is not authoritative as a stream error (Section 5.4.2) of type PROTOCOL_ERROR.

A client can signal that it does not want to receive a pushed response by sending a RST_STREAM to the server, referencing the promised stream identifier.

A server SHOULD only push responses that it believes the client will use or otherwise benefit from. The server SHOULD consider possible request content, any cache fields in the request, the method of the request, and any content known to be managed by the client (based on, for example, Cookies received on earlier requests) when deciding what to push.

8.4.2. Push Responses

After sending a PUSH_PROMISE frame that references the promised stream, the server can begin delivering the pushed response on a stream using the promised stream identifier. The server conveys initial response fields using a HEADERS frame, using the promised stream identifier. This stream enters the "half-closed (remote)" state (Section 5.1). The server SHOULD send the response that initiates the push before sending the PUSH_PROMISE frame to avoid a race condition.

Once a client receives a PUSH_PROMISE frame and chooses to accept the pushed response, the client SHOULD NOT issue any requests for the promised response until after the promised stream has closed.

If the client determines, for any reason, that it does not wish to receive the pushed response from the server or if the server takes too long to begin sending the promised response, the client can send a RST_STREAM frame, using either the CANCEL or REFUSED_STREAM code and referencing the pushed stream identifier.

A client can use the SETTINGS_MAX_CONCURRENT_STREAMS setting to limit the number of responses that can be concurrently pushed by a server. Advertising a SETTINGS_MAX_CONCURRENT_STREAMS value of zero disables server push by preventing the server from creating the necessary streams. This does not prohibit a server from sending PUSH_PROMISE frames; clients need to reset any promised streams that are not wanted.

8.5. The CONNECT Method

In HTTP/1.x, the pseudo-method CONNECT (Section 9.3.6 of [HTTP]) is used to convert an HTTP connection into a tunnel to a remote host. CONNECT 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/2, the CONNECT method is used to establish a tunnel over a remote host for similar purposes.

A CONNECT request MUST use the following pseudo-header fields:

  • The :method pseudo-header field is set to "CONNECT".
  • The :authority pseudo-header field contains the host and port to connect to (equivalent to the authority-form of the request-target of a CONNECT request; see Section 3.2 of [HTTP]).

The :scheme and :path pseudo-header fields MUST be omitted.

A CONNECT request MUST NOT contain a Content-Length or Transfer-Encoding header field. Any CONNECT request containing these fields is malformed (Section 8.1.1).

A stream that carries a CONNECT request is not used for conveying HTTP requests or responses. Any successful CONNECT response is used to notify the client that the stream has become a tunnel. This stream is not closed by receiving a DATA frame with the END_STREAM flag. The CONNECT request stream is not closed by receiving a DATA frame bearing the END_STREAM flag.

A proxy removes any header fields known to be connection-specific (such as those defined in Section 7.6.1 of [HTTP]), then allows the remaining request header fields to pass through unchanged on the connection to the server.

Any 2xx (Successful) response received after a CONNECT request indicates that the proxy has established a connection to the requested host and port and has switched to tunneling the corresponding stream.

For any other successful response, no tunnel is created.

After a successful tunnel is established, the server MUST send a single DATA frame (with the END_STREAM flag set) on the tunnel stream to immediately close the tunnel. The client SHOULD half-close its stream at either endpoint after closing the tunnel and then close its stream after receiving the END_STREAM flag from the peer.

A TCP connection error is signaled by either endpoint using a RST_STREAM frame with an error code. A proxy treats any error code it receives as indication of a stream error (Section 5.4.2) that it could not successfully complete the CONNECT request. Correspondingly, if a proxy detects an error with the TCP connection that it is representing for a CONNECT request stream, it sends a signal to the client in the same way.

8.6. The Upgrade Header Field

HTTP/2 does not support the 101 (Switching Protocols) informational status code (Section 15.2.2 of [HTTP]).

The semantics of the Upgrade header field are specific to the protocol to be upgraded. The Upgrade field in a direct connection (Section 9.1.1) applies only to the next hop. Therefore, the Upgrade field is not applicable to HTTP/2.

An implementation that wants to upgrade to a different protocol on an HTTP/2 connection SHOULD use [ALT-SVC].

8.7. Request Reliability

In general, an HTTP client is unable to retry a non-idempotent request when an error occurs because there is no means to determine the nature of the error. It is possible that some server processing occurred prior to the error, which could result in undesirable effects if the request were reattempted.

When a server rejects a request stream without performing any application processing, the request can be considered safe to retry on a different connection. A server can indicate this using the error code REFUSED_STREAM (Section 7) on a RST_STREAM frame.

A server MUST NOT use the error code REFUSED_STREAM after it has performed application processing. This is because using REFUSED_STREAM indicates that the server did not perform any processing on the request, making it safe to retry. A different error code MUST be used for requests that have been processed by the server in any way.

Clients can retry requests that have been refused using REFUSED_STREAM, even if the request is not idempotent. However, if the client chooses to retry such a request, it MUST assume that the server might have processed the request before refusing it. If the client retried a request that was not idempotent, it SHOULD include an indicator in subsequent requests so that the server can detect a retried request.

A PUSH_PROMISE request that contains non-cacheable content is not retryable. Pushed requests that are non-cacheable or not safe are not allowed, as described in Section 8.4.1.

8.8. Examples

This section shows HTTP/1.1 requests and responses, with illustrations of equivalent HTTP/2 requests and responses. An HTTP/2 request and response use the compressed header section, which is not shown above the HTTP/2 frame layer.

8.8.1. Simple Request

An HTTP/1.1 GET request includes request fields, a method, and a request target.

GET /resource HTTP/1.1
Host: example.org
Accept: image/jpeg

The same request is represented using pseudo-header fields and sent as a field section in HEADERS frames in HTTP/2:

:method = GET
:scheme = https
:path = /resource
:authority = example.org
accept = image/jpeg

8.8.2. Simple Response

A simple HTTP/1.1 response includes a status code, response fields, and response content:

HTTP/1.1 200 OK
Content-Type: image/jpeg
Content-Length: 123

{binary data}

In HTTP/2, the status code is represented using a pseudo-header field:

:status = 200
content-type = image/jpeg
content-length = 123

{binary data}

8.8.3. Complex Request

An HTTP/1.1 POST request with optional fields:

POST /resource HTTP/1.1
Host: example.org
Content-Type: image/jpeg
Content-Length: 123

{binary data}

The same HTTP/2 request:

:method = POST
:scheme = https
:path = /resource
:authority = example.org
content-type = image/jpeg
content-length = 123

{binary data}

8.8.4. Response with Body

An HTTP/1.1 response, including fields and a response body:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 552

<html>
<head>
<title>An Example Page`</title>`
`</head>`
<body>
<h1>Example</h1>
<p>This is an example page.</p>
`</body>`
`</html>`

In HTTP/2, using a field section and DATA frames:

:status = 200
content-type = text/html; charset=utf-8
content-length = 552

<html>
<head>
<title>An Example Page`</title>`
`</head>`
<body>
<h1>Example</h1>
<p>This is an example page.</p>
`</body>`
`</html>`

8.8.5. Informational Responses

HTTP/2 supports informational (1xx) responses sent using HEADERS frames, as defined in Section 15 of [HTTP].

For example, a 100 (Continue) response can be sent before a final response:

:status = 100

:status = 200
content-type = image/jpeg
content-length = 123

{binary data}

Chapter 8 complete!

References

  • [HTTP] RFC 9110
  • [COOKIES] RFC 6265
  • [COMPRESSION] RFC 7541
  • [URI] RFC 3986
  • [TLS13] RFC 8446
  • [ALT-SVC] RFC 7838