Using HTTP/2 Push on your web server
Introduction to HTTP/2 Server Push
The introduction of HTTP/2 has introduced a wealth of performance benefits, for most sites, without the website owner or developer having to make any changes at all. However one of the more interesting aspects, which will take some time to decide how to use correctly, is HTTP/2 Server Push. This allows the web server to push resources it thinks the browser will need to save time and that opens all sorts of opportunities (and dangers!).
Now there are some dangers to this. For example if you like this site so much, you might browse to another page. In this case you would have the css loaded (as I only use one css file across the whole site at the moment), but the web server might still attempt to "save time" by pushing the CSS on to you again. This is a waste of everyone's time and resources and can actually mean that you've made performance worse. Browsers can see that the push is being initiated (as the website will send a special PUSH_PROMISE message) and say "No thanks - already got that" with a special RST_STREAM message, but in a lot of cases by the time that gets to the browser it might have already sent the resource, or at least have fetched it and queued it up, so is not ideal.
So using HTTP/2 does require some extra thought rather than blindly pushing every resource. How exactly this will be managed is very much up in the air at the moment, though cookies are one suggestion to state whether a resource already exists on the client - though of course cookies are independent of the cache and one or the other can be cleared with the other still being present so it's not a perfect solution. Robin Marx published and excellent in-depth post on HTTP/2 Push covering a lot more detail than I intend to cover in this basic intro.
How to set up HTTP/2 Push
Apache added HTTP/2 push support in 2.4.18 but at the time of writing this was not available in Nginx or IIS. CloudFlare have also been doing a lot of work in this area, and use a modified version of Nginx so I imagine it won't be long before it lands in there since CloudFlare have kindly open sourced their Nginx HTTP/2 code. Also Akamai have a beta for HTTP/2 Server Push capability.
Both Apache and CloudFlare push based on a Link: Header (Akamai uses a slightly different method) so this Apache config would push my stylesheet with every request (including the stylesheet itself!):
Header add Link "</assets/css/common.css>;rel=preload;as=style"
It's important to note that the ";rel=preload;as=xxx" piece must be set: without the "rel=preload" Apache will not push the resource and without the "as=xxx" piece the pushed asset will be ignored by the browser and the asset with be downloaded a second time. The different link element extensions are defined here.
Note that in 2.4.24 Apache added the H2PushResource option as an alternative to using links, "to enable early pushes before processing of the main request starts". So this option is better if your request takes some time to process as the push requests will be sent back to he client while the request is being processed. This would be set like so:
This is easier to set as you don't have to set the ";rel=preload;as=xxx" piece. For now I've used the "Header add link" method as these examples are only set for static .html pages which are served quickly anyway, and also because I'm about to introduce a little trick using Apache environment variables.
HTTP/2 push could obviously be improved by only pushing this on page loads e.g. .html resources - note this site is based on static .html file rather than .php files, but you can change the config as appropriate:
<filesMatch "\.([hH][tT][mM][lL]?)"> Header add Link "</assets/css/common.css>;rel=preload;as=style" </filesMatch>
This will stop the stylesheet being pushed each time an image or the like is asked for. An even better implementation would be to prevent it being pushed when the browser already has the css file in it's cache. This could be achieved by setting a cookie, when the common.css file is pushed, and check that cookie next time, so you only push when the cookie is NOT set:
#Push the CSS file using HTTP/2 <IfModule http2_module> #Check if there's a cookie saying the css has already been loaded: SetEnvIf Cookie "cssloaded=1" cssloaded #If no cookie, and it's an html file, then push the css file #and set a session level cookie so next time it won't be pushed: <filesMatch "\.([hH][tT][mM][lL]?)"> Header add Link "</assets/css/common.css>;rel=preload;as=style" env=!cssloaded Header add Set-Cookie "cssloaded=1; Path=/; Secure; HttpOnly" env=!cssloaded </filesMatch> </IfModule>
This is fairly safe in that even if the css file is purged from the cache, then at worst the client will simply not benefit from push so no great loss. If the client disallows cookies completely (even first party session cookies), then it's a bit more damning as Apache will attempt to push the css file each time. It's a small file, and this is but a simple personal website so I'm OK with that as I'm really just experimenting for now, but larger sites should have a long hard think about these sorts of issues before enabling server push.
The above implementation is a very simple way of trying to ensure you don't push resources that are already in the cache, but it would be much better if the browser (which ultimately knows what is in the cache), told the server on each request, in an efficient way, rather than trying to guess it with cookies. This is the idea behind HTTP/2 Cache Digests, but they are still being defined so, at the time of writing, they are not yet available to be used but keep an eye on the IETF specification for when they become standardised.
If you are using other technology behind Apache, to generate the pages (e.g. PHP or an App server such as Jboss, Tomcat, Node...etc.), then you can use them to set the headers appropriately, rather than Apache itself, and Apache should read those headers when sending the response and push appropriately.
When you load this page fresh in Chrome, with developer tools open (and with that cookie cleared) you should see the Initiator as "Push / Other" and also a very short download time, with no Green waiting time for the first byte:
As mentioned above, support of this is fairly light at the moment and most people who can benefit this will be using Apache, CloudFlare or Akamai. I'm not sure what other servers support this (nghttp2 does as this is the underlying code for Apache and Nginx as well as a few others) but do expect this list to grow.
On the client side, all the modern browsers but Safari support HTTP/2 Push. However they all treat a push as a push to cache, so only cacheable resources that would be used on the page anyway can be pushed. This of course also means that you need to ensure you are setting HTTP Caching Headers on these resources so when it's pushed, it's cached and then used by the page!
The three main downsides are: 1) Support is limited at the moment (see above), 2) There are dangers of over pushing and 3) It's more complexity! At the moment it's definitely an experimental feature and probably not something most sites should turn on, except perhaps for very simple use cases (like mine above). However it is definitely something to keep an eye on as it is one of the more interesting parts of HTTP/2 that is likely to lead to a lot of innovation in the future.
HTTP/2 Server Push is available now in some HTTTP/2 implementations and offers some interesting performance options (and dangers!) to address download times for websites.
Want to read more?
More Resources on HTTP/2
- My original HTTP/2 post.
- HTTP/2 Push: The details by Robin Marx published as part of the Annual Performance Calender series
- A Closer Look to HTTP/2 Push is an interesting post on this subject by Shimmercat.
- HTTP/2 Spec which introduces HTTP/2 Server Push, but left a lot of the details of how to do this out of the RFC.
- Addition of HTTP/2 Push to the Preload Link Spec
This page was originally created on and last edited on .Tweet