Issue 5: HTTP/2 Push

HTTP/2! Our glorious saviour from all the awkward workarounds that HTTP/1 made us do to have a decently performing web app. I hope you are using it already. Since CloudFlare added support for HTTP/2, it has gotten a lot easier to switch to HTTP/2 and adoption has been steadily rising ever since. All the latest versions of browsers support it as well, so not only is it a good time to optimize for HTTP/2, but you can most likely safely ignore HTTP/1 at this point. I thought it was about time to take a look at HTTP/2’s infamous push feature. And then reality happened.

HTTP/2 push is a feature that lets you push a response to the client’s cache* that it hasn’t even requested yet. This will allow you to use the network while the browser is too busy parsing the HTML document. You can provide the resources you know the browser will need any moment now. This is yet another HTTP/2 knob you can use to turn your web app loading performance up to 11.

*) Not correct. See below.

The app

I wrote a little node app using node-http2 that starts an HTTP/2 web server and serves an HTML file that will load an image after a 5 second timeout.

Yes, I wrote this code myself!

The web server uses HTTP/2 push to send that image to the browser when a request for `index.html` comes in. But how would I know if it worked? DevTools didn’t show me when resources were pushed. My idea was that I would use DevTools’ network throttling feature to make the image load slow. In normal circumstances the image would load progressively due to the throttling. If the image got pushed however, the 5 second timeout should be enough for the picture to be completely loaded into the browser’s cache and appear instantly. But no matter how I turned and twisted my code, the image would always load progressively and never instantly — until I unchecked “Disable cache” in DevTools. I have this option enabled almost all the time for development. Immediate conclusion: HTTP/2 needs the browser’s cache to be enabled to work! Interesting. This led me to make this tweet, which got decent exposure. And it’s wrong! Factually wrong. I failed y’all.

My tweet of shame

Fact #01: You can see pushes in DevTools’ network panel 🔎

I wouldn’t have to do this arcane throttling-and-load-an-image dance to figure out if something had been pushed if I had tried to Chrome Canary first*. In Canary the network panel actually marks the “Initiator” of a request as “Push”.

*) In my defence: I did check Canary, but for some reason the initiator column was hidden.

DevTools in Canary marks pushed resources

But since I found out about this feature a little late, I also wrote http2-push-detect. It’s a simple node.js CLI tool that lists all pushed resources for a given URL.

Installing & using http2-push-detect

Fact #02: In Chrome*, pushed resources are stored in a magical place before the HTTP cache 🌈

My colleague Ilya Grigorik educated me that — in Chrome — pushes are not pushed into the browser’s HTTP cache but stored in a place that sits between the network and the HTTP cache. This was news to me. I actually gave a talk about HTTP/2 at Chrome Dev Summit 2015, where I claimed push puts things into your browser’s cache. Although that is true eventually, it is not a guarantee. A pushed resource is not committed into the HTTP cache until it is matched by a request from the application. These pushed items will stay in this magical place for the duration of the HTTP/2 session. Meaning they can survive navigation and a certain amount of time, but will be dropped once the session ends.

*) I have yet to figure out the details for other browsers.

Fact #03: There’s a bug in Chrome DevTools that makes push and throttling not play nice 🐞

After learning fact #01 & #02, I restarted my experiment. But alas, the image kept loading progressively even though the network panel now showed it as a push resource. What was I doing wrong? We all know that there’s never any bugs in Chrome, so that wasn’t an option. But eventually I thought I’d better double check. Instead of relying on DevTools to do the throttling, I implemented throttling in my node app with the stream-throttle module. Lo and behold! I was rewarded with the behavior I was expecting all along. I even found the Chrome bug in our issue tracker. It turns out that DevTools’ throttling feature sits before the magical HTTP/2 push cache. So even if the resource is technically locally available, Chrome will pretend to make a roundtrip.

Bottom line: This just showed me how new HTTP/2 still is and that there’s a lot of explorations and tests to be done. With design patterns like PRPL arising, we will have to collect much more detailed knowledge to make informed decisions about what is best for our apps.