Two weeks ago CloudFlare announced that it was supporting HTTP/2 Server Push for all our customers. By simply adding a Link
header to an HTTP response specifying preload
CloudFlare would automatically push items to web browsers that support Server Push.
To illustrate how easy this is I create a small PHP page that uses the PHP header
function to insert appropriate Link
headers to push images to the web browser via CloudFlare. The web page looks like this when loaded:
There are two images loaded from the same server both of which are pushed if the web browser supports Server Push. This is achieved by inserting two Link
headers in the HTTP response. The response looks like:
HTTP/1.1 200 OK
Server: nginx/1.9.15
Date: Fri, 13 May 2016 10:52:13 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
Link: </images/drucken.jpg>; rel=preload; as=image
Link: </images/empire.jpg>; rel=preload; as=image
At the bottom are the two Link
headers corresponding to the two images on the page with the rel=preload
directive as specified in W3C preload draft.
The complete code can be found in this gist but the core of the code looks like this:
<?php
function pushImage($uri) {
header("Link: <{$uri}>; rel=preload; as=image", false);
return <<<HTML
<img src="{$uri}">
HTML;
}
$image1 = pushImage("/images/drucken.jpg");
$image2 = pushImage("/images/empire.jpg");
?>
<html>
<head><title>PHP Server Push</title></head>
<body>
<h1>PHP Server Push</h1>
<?php
echo ccbysa($image1, "https://bit.ly/1Wu5bYx",
"https://www.flickr.com/photos/hiperactivo/", "Javier Candeira");
echo ccbynd($image2, "https://bit.ly/24PHue3",
"https://www.flickr.com/photos/bobsfever/", "Robert McGoldrick");
?>
</body>
</html>
Since you have to call the PHP header
function before any output (such as HTML) has occurred the code makes two calls to a helper function called pushImage
first. pushImage
adds the appropriate Link
header and returns the HTML needed to insert the actual image in the page.
Later the variables $image1
and $image2
(which contain the HTML needed to display the images) is inserted use two other helper functions (ccbysa
and ccbynd
) that add captions. Those two helper functions don't play any part in the Server Push, they just ensure that the HTML is placed correctly on the page with a caption.
Notice that the Link
header is added as follows:
header("Link: <{$uri}>; rel=preload; as=image", false);
The false
second parameter tells header
to not override an existing Link
header. With that option specified multiple Link
headers can be added. Without it the last call to pushImage
would win.
Effect of Server Push
To understand the effect of Server Push on the example I used Google Chrome Canary and loaded the page twice (once without the Link
headers so that no push would occur and once with).
Here's the simple waterfall over HTTP/2 with no Server Push:
The page load time was 651ms. You can see the initial page HTML load in 175ms followed by the two images.
How here's the waterfall with HTTP/2 Server Push:
The page load time was 251ms (the HTML loaded in 168ms) and Chrome is showing that the two images were pushed (see the Initiator column).
It's not 100% obvious what happened there but digging into chrome://net-internals/ it's possible to see a detailed HTTP/2 timeline. I've edited out a few details of the protocol (such as the window size changes) to focus in on the requests and responses.
The t
value gives the tick time (1ms per tick).
t=910212 [st= 1] HTTP2_SESSION_SEND_HEADERS
--> exclusive = true
--> fin = true
--> has_priority = true
--> :method: GET
:scheme: https
:path: /index.php
pragma: no-cache
cache-control: no-cache
upgrade-insecure-requests: 1
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
accept-encoding: gzip, deflate, sdch, br
accept-language: en-US,en;q=0.8
cookie: [52 bytes were stripped]
--> parent_stream_id = 0
--> priority = 0
--> stream_id = 1
[...]
t=910404 [st= 193] HTTP2_SESSION_RECV_HEADERS
--> fin = false
--> :status: 200
date: Fri, 13 May 2016 11:28:16 GMT
content-type: text/html
content-encoding: gzip
--> stream_id = 1
t=910405 [st= 194] HTTP2_SESSION_RECV_PUSH_PROMISE
--> :method: GET
:path: /images/drucken.jpg
:scheme: https
accept-encoding: gzip, deflate, sdch, br
--> id = 1
--> promised_stream_id = 2
t=910405 [st= 194] HTTP2_SESSION_RECV_PUSH_PROMISE
--> :method: GET
:path: /images/empire.jpg
:scheme: https
accept-encoding: gzip, deflate, sdch, br
--> id = 1
--> promised_stream_id = 4
t=910405 [st= 194] HTTP2_SESSION_RECV_DATA
--> fin = false
--> size = 298
--> stream_id = 1
t=910405 [st= 194] HTTP2_SESSION_RECV_DATA
--> fin = true
--> size = 0
--> stream_id = 1
t=910409 [st= 198] HTTP2_SESSION_RECV_HEADERS
--> fin = false
--> :status: 200
date: Fri, 13 May 2016 11:28:16 GMT
content-type: image/jpeg
content-length: 49852
set-cookie: [124 bytes were stripped]
etag: "5735aac0-12f99"
last-modified: Fri, 13 May 2016 10:21:52 GMT
cf-cache-status: HIT
vary: Accept-Encoding
expires: Fri, 13 May 2016 15:28:16 GMT
cache-control: public, max-age=14400
accept-ranges: bytes
server: cloudflare-nginx
--> stream_id = 2
t=910409 [st= 198] HTTP2_SESSION_RECV_DATA
--> fin = false
--> size = 987
--> stream_id = 2
t=910409 [st= 198] HTTP2_SESSION_RECV_DATA
--> fin = false
--> size = 1369
--> stream_id = 2
t=910410 [st= 199] HTTP2_SESSION_RECV_DATA
--> fin = false
--> size = 1369
--> stream_id = 2
The web browser sends an HTTP2_SESSION_SEND_HEADERS
request asking for the web page (that's the first item above) and subsequently receives the response headers and page followed immediately by two push promises for the two images. It then immediately starts receiving image data (see the stream_id
2).
CloudFlare's web server is pushing the image contents before the browser asked for them. When CloudFlare pushes an item specified in a Link
header the header itself is stripped (to prevent the browser from re-requesting the resource).
The Future Starts Here
We released HTTP/2 Server Push support to help kick start innovative use of this critical feature of HTTP/2. We would love people to start experimenting with it to see how much of a speed improvement is possible with their specific websites.
As can be seen from this blog post making use of Server Push in a web application is easy: just insert Link
headers with the appropriate format. Server Push will be particularly fast if the items being pushed are also stored in CloudFlare's cache (as was the case with the examples above).
Please let us know how you use Server Push. We're particularly interested in experiences with pushing different types of resources (images vs. styles vs. scripts) and working out the optimal number of items to push (we currently allow up to 50 resources per page).