HTTP/2 Server Push
HTTP/2 Server Push allows an HTTP/2-compliant server to send resources to a HTTP/2-compliant client before the client requests them. It is, for the most part, a performance technique that can be helpful in loading resources preemptively.
HTTP/2 Server Push[1][2] is not a notification mechanism from server to client. Instead, pushed resources are used by the client when it may have otherwise produced a request to get the resource anyway; this can result in wasted bandwidth if said pushed resources go unused by the client, however.
Basic concept
Consider a website with three resources: index.html, styles.css and script.js. When a user, through their browser, connects to the home page of this website they automatically retrieve index.html. As the browser parses the HTML text in index.html, it finds instructions that will require styles.css and script.js. At that point, the browser will issue requests to get these other two files. To assemble the complete webpage, the browser will stack such requests as it gradually discovers the site's composition.
With HTTP/2 Push, the server can take the initiative by having rules that trigger content to be sent even before it is requested. In this example scenario, the server knows that anyone requesting index.html will need styles.css and script.js, so it can push them to the client immediately without waiting for the client to request them. If done correctly, by the time the browser finishes parsing index.html, the transfer of styles.css and script.js would have already started, or even completed, removing the latency of having to request these and wait for them to arrive.
How HTTP/2 PUSH works at a protocol level
Push works over HTTP/2, which at its core is a frames protocol, meaning that information is exchanged in groups of bytes called frames. Additionally, frames are part of streams, and streams are identified by a number. The stream number is present in each frame as a binary field. Streams allow matching requests to responses, e.g. the response to request GET /index.html at stream 3 must also be at stream 3.
There are different types of frames, and each has a different function. HTTP/2 has only a few of these types, and we don't need all of them to explain the basics. Here are the ones interesting for this description:
- HEADERS frame. As its name implies, this type of frame carries HTTP headers . When sent by the browser to the server, it signals that a request is being made. When sent by the server to the browser, it signals that a response to a previous request or push promise is being sent.
- PUSH_PROMISE frame. This frame is sent by the server to the browser to start pushing a resource. It also contains HTTP headers. However, the kind of headers present in a PUSH_PROMISE frame are headers that would normally be present in a request. This is different from the response headers that a server would normally send. The request URL, for example, is present in the PUSH_PROMISE frame as the HTTP/2-specific :path pseudo-header, as is the :authority pseudo-header to indicate a host. Other headers that may be present in a PUSH_PROMISE and that some browsers use are cache headers, for example, if-none-match.
- DATA frames. These frames are sent in either direction to carry the actual content of a resource or the contents that the browser POSTs or PUTs to the server.
- RST_STREAM frames. These frames serve many purposes. One of them is having the browser signal to the server that a pushed stream is not needed.
When the server wants to push a resource, it prepares a PUSH_PROMISE frame, architecting it in the best way possible to seduce the browser into using the pushed contents. Then the server annexes the PUSH_PROMISE frame to the response part of a normal, browser-initiated stream. However, the actual data for the pushed resource is sent in a fresh stream started by the server – and thus with an even number.
The browser holds the pushed data in a temporary "quarantine" zone until it decides to use it. Later, every time the browser is going to make an actual request, it examines the contents of any received push promises to see if it is similar enough to the request it wants to make. However, the server need not wait until that moment to start sending data for the promised resource. After the PUSH_PROMISE frame has been sent on the browser-initiated stream, the server can send what would be response headers using a HEADERS frame in the new server-initiated stream, and later it can send the data of the resource using DATA frames. And at any point in time, the browser can interrupt any transfer by using RST_STREAM.
Here is how this would work in the previous example. If the server is HTTP/2 PUSH ready, when it receives a request to index.html it can forecast that requests to styles.css and script.js are following close in tail. So it issues push promises to get a bit ahead of events. Here is how things could look, in order of occurrence and making up the stream numbers:
- Server receives HEADERS frame asking for index.html in stream 3, and it can forecast the need for styles.css and script.js.
- Server sends a PUSH_PROMISE for styles.css and a PUSH_PROMISE for script.js, again in stream 3. These frames are roughly equivalent to a browser's request.
- Server sends a HEADERS frame in stream 3 for responding to the request for index.html.
- Server sends DATA frame(s) with the contents of index.html, still in stream 3.
- Server sends HEADERS frame for the response to styles.css in stream 4 – notice the even stream number – and then for the response to script.js in stream 6.
- Server sends DATA frames for the contents of styles.css and script.js, using their respective stream numbers.
Push promises are sent as early as possible so that the browser will have them well ahead of any discoveries. Notice that HTTP headers (specifically Link with the 'preload' keyword[3]) can reveal URLs that the browser needs to fetch, and an eager browser would start asking for the resources upon seeing those headers. Therefore, push promises are best sent before even the response headers of the stream where they are attached.[4]
Implementations
HTTP/2 Server push is being progressively implemented, for example Nginx web server implemented it in version 1.13.9 in February 2018.[5]
According to the Google Chrome team, Server Push in HTTP/2 and gQUIC is rarely used, and the pushed resources are more often unused than used. They have proposed to remove the feature from Chrome and Chromium.[6]
References
- "HTTP/2 server configurations". Retrieved 2019-03-30.
- "Server Push". Hypertext Transfer Protocol Version 2 (HTTP/2). IETF. May 2015. p. 60. sec. 8.2. doi:10.17487/RFC7540. RFC 7540. Retrieved May 6, 2015.
- "Preload". w3c.github.io. Retrieved 2016-11-30.
- "A closer look to HTTP/2 Push". ShimmerCat.
- https://nginx.org/en/CHANGES-1.14
- https://groups.google.com/a/chromium.org/g/blink-dev/c/K3rYLvmQUBY/m/vOWBKZGoAQAJ?pli=1