HTTP/2
HTTP/2 is a major revision of the HTTP protocol. It was initially was based on SPDY, an experimental protocol developed by Google, but it has since evolved beyond that, receiving additional improvements, such as the new header compression algorithm.
HTTP/2 does not overhaul HTTP/1 completely. It remains conceptually very similar to HTTP/1 while offering substantial performance improvements.
For instance, HTTP/2 does not modify application semantics, so it is possible to use the same applications and APIs as in HTTP/1: methods, status codes, header fields, and URIs remain unchanged. The introduced changes mostly concern how the exchanged data is framed and transported over the network.
An example with cURL
As an example, let's use cURL
to send a HEAD
request using the HTTP/1.1
and HTTP/2
and compare responses; HEAD
request is basically the same as GET
, except that the server returns only the header while omitting the body in the response (we are not currently interested in the body of the response):
curl -I --http1.1 https://www.youtube.com
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Content-Type-Options: nosniff
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: Mon, 01 Jan 1990 00:00:00 GMT
Date: Fri, 27 May 2022 13:09:15 GMT
Content-Length: 737084
Strict-Transport-Security: max-age=31536000
X-Frame-Options: SAMEORIGIN
Permissions-Policy: ch-ua-arch=*, ch-ua-bitness=*, ch-ua-full-version=*, ch-ua-full-version-list=*, ch-ua-model=*, ch-ua-platform=*, ch-ua-platform-version=*
Cross-Origin-Opener-Policy-Report-Only: same-origin; report-to="youtube_main"
Report-To: {"group":"youtube_main","max_age":2592000,"endpoints":[{"url":"https://csp.withgoogle.com/csp/report-to/youtube_main"}]}
P3P: CP="This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl=sl for more info."
Server: ESF
X-XSS-Protection: 0
Set-Cookie: YSC=jkMhm-wXTb8; Domain=.youtube.com; Path=/; Secure; HttpOnly; SameSite=none
Set-Cookie: VISITOR_INFO1_LIVE=; Domain=.youtube.com; Expires=Sat, 31-Aug-2019 13:09:16 GMT; Path=/; Secure; HttpOnly; SameSite=none
3D%3D; Domain=.youtube.com; Expires=Mon, 26-Jun-2023 13:09:14 GMT; Path=/; Secure; HttpOnly; SameSite=lax
Set-Cookie: CONSENT=PENDING+091; expires=Sun, 26-May-2024 13:09:15 GMT; path=/; domain=.youtube.com; Secure
Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
Here, we see a typical header of a HTTP/1.1
response. The first line, called the status line, contains the version of the protocol, response code and reason phrase. We then have a list of key-value pairs; they are called HTTP response headers, which give information either about the server or the response content.
If we send the same request using the HTTP/2
, we get basically the same information:
curl -I https://www.youtube.com
HTTP/2 200
content-type: text/html; charset=utf-8
x-content-type-options: nosniff
cache-control: no-cache, no-store, max-age=0, must-revalidate
pragma: no-cache
expires: Mon, 01 Jan 1990 00:00:00 GMT
date: Fri, 27 May 2022 13:09:16 GMT
content-length: 675371
x-frame-options: SAMEORIGIN
strict-transport-security: max-age=31536000
cross-origin-opener-policy-report-only: same-origin; report-to="youtube_main"
permissions-policy: ch-ua-arch=*, ch-ua-bitness=*, ch-ua-full-version=*, ch-ua-full-version-list=*, ch-ua-model=*, ch-ua-platform=*, ch-ua-platform-version=*
report-to: {"group":"youtube_main","max_age":2592000,"endpoints":[{"url":"https://csp.withgoogle.com/csp/report-to/youtube_main"}]}
p3p: CP="This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl=sl for more info."
server: ESF
x-xss-protection: 0
set-cookie: YSC=JnDX3aAE43w; Domain=.youtube.com; Path=/; Secure; HttpOnly; SameSite=none
set-cookie: VISITOR_INFO1_LIVE=; Domain=.youtube.com; Expires=Sat, 31-Aug-2019 13:09:17 GMT; Path=/; Secure; HttpOnly; SameSite=none
3D%3D; Domain=.youtube.com; Expires=Mon, 26-Jun-2023 13:09:15 GMT; Path=/; Secure; HttpOnly; SameSite=lax
set-cookie: CONSENT=PENDING+734; expires=Sun, 26-May-2024 13:09:16 GMT; path=/; domain=.youtube.com; Secure
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
While the "reason phrase" in the response line is omitted, the rest is virtually the same.
The main focus of HTTP/2 is performance: specifically, on end-user perceived latency, network and server resource usage. One major goal of HTTP/2 is to allow the use of a single underlying TCP connection between the client and the server. An analogy between HTTP and HTTP/2:
Imagine calling and hanging up when you finish your message. With HTTP/2, you maintain the call even if nothing is happening (refer to note below), allowing for messages to be communicated in a streamlined fashion.
Note: Most webservers have different "keep alive" timeouts.
Main differences between HTTP/1 and HTTP/2 protocols
Compared to HTTP/1, HTTP/2 has the following properties.
- It is binary, not text encoded.
- It is fully multiplexed, while using a single underlying TCP connection.
- It uses a header compression algorithm that drastically reduces the size of HTTP headers over plain-text.
- It allows servers to anticipate which resources will clients request next and enables them to pro-actively push said resources to clients without the latter having to request them explicitly.
- It improves security.
Let's take a look at a few of these issues more closely.
Binary encoding
The encoding defines how data is represented on the wire. In HTTP/2, all data is binary encoded as opposed to text-encoded in HTTP/1. Furthermore, all communication is made over a single underlying TCP connection. Such connection contains multiple streams where a stream is simply a bidirectional flow carrying multiple HTTP request and response messages. Each stream has an identifier and, optionally, a priority value.
HTTP messages, either requests or responses, are further broken down into frames. These are the smallest unit of exchange in HTTP/2. A frame, for instance, can represent an header of an HTTP request or a response and similar.
As an example, imagine we want to obtain files index.html
and style.css
. In HTTP/2, as in HTTP/1, a client would send two HTTP requests and receive two HTTP responses. However, in HTTP/2, these exchanges would occur over the same TCP connection and the requests and responses would be broken down into frames.
Let’s say that each HTTP request is represented with a single frame (although it could be more), while each HTTP response spans at least two frames: one for HTTP response headers and the other for the HTTP response body. Furthermore, such exchange would occur over two streams: the first stream would contain frames belonging to the request-response cycle regarding resource index.html
and the other to resource style.css
.
Additionally, the order in which the frames representing the HTTP response are received might not be the same as the one in which the HTTP request frames were sent: frames from different streams may be interleaved and it is the responsibility of the recipient to reassemble and assign them to correct streams.
In summary, HTTP/2 breaks down HTTP requests and responses messages into binary-encoded frames. These are assigned to different streams and exchanged over a single TCP connection.
Multiplexing requests and responses
In HTTP/1, if we want to send parallel requests to decrease page load times, we have to use multiple TCP connections. While a single TCP connection could be reused using the connection: keep-alive
request header, this requires synchronous communication. Before sending a new request, the previous request needs to be fulfilled. This causes the so called "head-of-line" problem.
The "head-of-line" problem occurs when an entire line of messages (HTTP requests) is held up because the first message in the line is blocked (and cannot proceed until it is un-blocked).
With HTTP/1, up to 6 simultaneous connections were used. This, however, merely delays the problem while increasing the amount of overhead (TCP connections are not cheap!). The problem is intensified when adding security features: every TCP connection needs to be secured independently of one another.
Another solution for this in HTTP/1 was HTTP pipelining. This allowed clients to have multiple outstanding HTTP requests. The servers, however, had to respond in order. This optimization brought speed-ups mostly to high-latency connections, such as satellite links. On broadband connections, the head-of-line blocking issue remained.
The example in previous section -- requesting index.html
and style.css
-- shows how such problems in HTTP/2 simply cannot occur: since the HTTP request and response messages are broken into independent frames (which are sent in arbitrary order) and reassembled at the destination, blocking at the head of line is never an issue. Furthermore, since streams have priorities and frames can be sent in any order, the communicating parties can negotiate which requests are prioritized.
Server push
Another novelty in HTTP/2 is the ability of the server to send multiple responses to a single request. For instance, the server may anticipate which resources the client is likely to request next, and sends them in advance.
While this breaks the traditional request-response semantics, it offers new application possibilities. One immediate benefit is performance improvement: if the client requests resource index.html
that hyperlinks to a bunch of other resources, such as images, JavaScript and CSS files, the server can resonably expect that these resources will be requested next, and simply push them to the client in advance.
If the client then actually uses these resources, the server has saved a bunch of HTTP requests. However, such pushing needs to be managed carefully: if the client already has them in cache, the bandwidth is wasted, not saved.
Server push is therefore negotiated with the PUSH_PROMISE
and RST_STREAM
frames: the first allows the server to announce which resources it is about to push, and the second allows the client to decline them.
While HTTP/1 does not have such server push option, a typical optimization is to inline resources into the HTML document; inlining saves further request-response cycles. However, when resources are inlined, they are always sent. Even if clients alredy have them in cache – there is no possibility to opt-out.
Compressing HTTP headers
HTTP headers contain HTTP request meta-data. Headers contain information about the request, the response, the client or the server. Since the information about the client and the server does not change often, the header values are often redundantly repeated.
HTTP/1 always sends headers as plain-text and never compresses them. On average, each HTTP header contains between 500-800 bytes, and sometimes this number baloons over a kilobyte, if for instance cookies are used.
HTTP/2 introduces the HPACK compression format that compresses HTTP headers using two simple techniques:
- It uses a list of static Huffman codes to compress typical header names and values.
- To compress non-typical (and repeating) headers, the server and the client maintain a list of previously seen headers. When a header repeats they simply refer to it by an index in said list. This means that a header is sent at most once; when it repeats (which is typical with headers), all that is sent is the index value.
Improved security
While HTTP/2 does not introduce new security features, it nonetheless brings two security-related improvements. First, while the standard does not mandate the use of encryption, no browser implementation today supports HTTP/2 over unencrypted TCP connection. And second, since all exchanges happen over a single underlying TCP connection, less TLS handshakes are made, sessions are better reused, and consequently less resources are wasted.