Implementing HTTP/2 server push

July 6, 2016

GravatarBy Kazu Yamamoto

What is HTTP/2 server push?

Server push is a hot topic of HTTP/2. Effective usage of server push can improve user experience. For instance, let's consider a scenario: downloading "index.html" which requires "style.css".

If a browser uses HTTP/1.1, it gets the "index.html" first, parses it, then obtains "style.css". So, two round-trip-times (RTTs) are necessary before the browser starts rendering.

On the other hand, if a browser uses HTTP/2 and the corresponding server supports server push, only one RTT is necessary. That is, when the browser send a GET request for "index.html", the server pushes "style.css" before it transfers "index.html". When the browser parses "index.html", it finds "style.css" in its local cache. Thus, it can start rendering at this timing.

APIs for server push

So, how can we implement HTTP/2 server push in Warp? Clearly, it is necessary for an application to tell Warp what files should be pushed. One way is to extend the Response type to store information on server push. But this breaks compatibility. We need to modify all existing applications.

To keep all existing types as is, I decided to use vault in the Request type. Vault is a heterogeneous infinite map which can store any type. Warp creates a new IORef and store its getter and setter to the vault:

getHTTP2Data :: Request -> IO (Maybe HTTP2Data)
setHTTP2Data :: Request -> Maybe HTTP2Data -> IO ()

HTTP2Data is the data type to store a list of files to be pushed. An application can set files to be pushed by setHTTP2Data. Warp retrieves them by getHTTP2Data and pushes them before sending the corresponding Response.

Note that the vault is a part of Web Application Interface (WAI) but these two functions are not. They are APIs provided only by Warp.

Middleware for server push

The next question is how an application knows which files to be pushed for a given URL. One way is manifest files.

Another way is learning based on Referer:. This idea came from Jetty. Typically, there is the Referer: whose value is, say, https://example.com/index.html", in a GET request to "style.css". So, analyzing requests for a while, we can learn that "style.css" should be pushed when "index.html" is requested.

@davean suggested that I implement this mechanism as a middleware. So, I created a middleware for HTTP/2 server push based on Referer:. Its default behavior for a given Request and Response is as follows:

  • If files to be pushed are found for a given path in a learning dictionary, set them by setHTTP2Data.
  • Otherwise, register the file of Response to the learning dictionary only when the following conditions are met:
  1. The Request stores a valid Referer:
  2. The Response is a file type
  3. The path of Referer: is "/" or ends with ".html"/".html"
  4. The path ends with ".js" and ".css"

The learning dictionary is cleared every 30 seconds.

Warp v3.2.7 with the push APIs and wai-http2-extra v0.0.0 with the middleware are already on Hackage. If you implement a server push middleware based on manifest files, please send a pull request on github.

Visualization

Here are screen shots of Firefox accessing the new Warp. The first figure is the first access and the middleware has not learned anything. So, no pushes are used.

Figure 1: Download time-line without server push

The second figure is the second access. You can see .js and .css files are pushed since bullets are not green.

Figure 2: Download time-line with server push

Next Step

The next step would be an implementation of Cache Digests for HTTP/2. In this scheme, a browser can tell a list of cached file to a server. So, the server can avoid unnecessary pushes.

Acknowledgment

Efforts to bring HTTP/2 server push feature to Warp was originally made by Andrew Pritchard. Without his experience and suggestions, this work would be impossible. I thank him deeply.

Comments

comments powered by Disqus

Archives