Using Cloudflare's HTTP/2 Server Push
For those unaware, when using Cloudflare to proxy your site, HTTP/2 support is usually on by default, and highly recommended, and you can also enable HTTP/3 too. But, for a while I thought that Cloudflare’s HTTP/2 would rob you of one of the really cool and touted features of HTTP/2: the server push. Well, as it turns out, that’s completely possible. Let’s dive in.
HTTP/2 Server Push, Explained
The normal flow of HTTP has been, for a while now, you make a request, you get a response.
The client is the one asking for resources, and the server is the one sending them.
Besides the other benefits of HTTP/2, which I’m not explaining right now as that’s for another post, but I’ll explain it as this: HTTP/2’s protocol is a series of binary “frames” sent between the client and the server, usually with the client asking for a resource and the server sending it.
However, the server may send the client a PUSH_PROMISE
frame, in which it contains a valid request.
Unless the client cancels this, the server will behave as if the request in that frame was sent to it by the client, and it’ll send the resource over.
In effect, the server can synthesize “fake” requests to respond to, and then respond to them.
When used correctly, this can allow for one to do things like push over resources like a page’s JS or CSS that it’s guaranteed to require, allowing those to download while the client is still reading through the main page to assemble it, thus saving time compared to the client needing to download a resource, then read it up until it sees that it needs something else, then grab that…
In most HTTP servers, it’s pretty simple to tell them when and what to push.
Apache, for example, inspects the Link
headers set in the response, so adding Header add Link
… lines to a Location
are what you’d use.
With NGINX, you add http2_push
directives in a location
block.
Caddy uses the push
directive… they’re all pretty simple.
Telling Cloudflare What to Push
My initial thoughts told me that HTTP/2 would be nearly wasted with Cloudflare, since they will only use HTTP/1.1 to connect to your backend… I kinda forgot that their cache exists for a second.
If all HTTP/2 traffic is being converted to HTTP/1.1, you’ve even lost the benefit of multiple streams per connection, but if you’re not, say, because cache, that’s better.
But, that out of the way, if the backend can’t push to Cloudflare, how can Cloudflare push to clients?
Well it turns out they did add a feature for this, and it’s one I kinda said just a bit ago: They inspect the Link
headers of the response.
Any resources given a preload
relation in a Link
header will be HTTP/2 pushed to the connecting client if they can.
If the resources are in Cloudflare’s cache, they’ll get pushed over, otherwise they’re fetched, then pushed.
I do find it odd that the Link
headers are passed through anyways, but… either way, here’s an example:
Link: </theme.CSS>; rel=preload, </medium-zoom.js>; rel=preload;
And if you’ve not noticed, I’ve already done this, so any reports from your inspector should tell you that most CSS, JS, and even fonts that I’m using are getting pushed to you if they’re not already cached by your browser.
This header would, when sent to Cloudflare (or, Apache), tell it to start pushing the theme.css
and medium-zoom.js
files to the client.
A Little More Information: The Link
Header
If you’re curious, the Link
header is a spin-off of the <link>
HTML tag, which is used… to tell a client how various resources link to each other.
Normally you’d find <link>
s in the <head>
of the HTML page, but if you add them as a header, then the client can see and act on those just a little bit faster.
Also, unlike the tag, the header can contain multiple links at once.
The <link>
Tag
The link tag has been used for ages to add style sheets to your page, which, at this point, are a necessity, but it can do more than that, depending on the relation. Link tags, if you’re not aware, look like this:
<link href="/theme.css" type="text/css" rel="stylesheet" />
They specify the resource (href
), it’s MIME type (type
, optional), and a relation (rel
) to the current resource, but they can add other parts too.
Link Relations
Each relation can mean something different, preload
usually means to preemptively connect and get ready to download, feed
would be the website’s RSS feed, stylesheet
is… a stylesheet, there’s an entire list.
103 Early Hints
If you’ve read the Apache documentation, you’d know about this one.
HTTP’s 1xx
codes are “informational” in that they don’t signify the end of a particular exchange, it’s just the server remarking about something.
103 Early Hints
would be used to, say, send a bunch of Link
headers to the client before the server has fully processed their request and is ready to send response data to the client, so the client can prepare by either getting ready to load the resources it needs, or accepting those PUSH_PROMISE
s.
While.. a Link
header is just a Link
header, the 103 Early Hints
response was made pretty much entirely to send Link
headers to the client for it to get to preloading resources while the server is still preparing a response.
I do find it funny though — If you send 103 Early Hints
to Cloudflare’s servers, they have basically no idea what to do with that and… really do not like it.
So uh… don’t do that.