| HTTP/2 Server Push |
| 24 Mar 2017 |
| Tags: http, technical |
| |
| Jaana Burcu Dogan, Tom Bergan |
| |
| * Introduction |
| |
| HTTP/2 is designed to address many of the failings of HTTP/1.x. |
| Modern web pages use many resources: HTML, stylesheets, |
| scripts, images, and so on. In HTTP/1.x, each of these resources must |
| be requested explicitly. This can be a slow process. |
| The browser starts by fetching the HTML, then learns of more resources |
| incrementally as it parses and evaluates the page. Since the server |
| must wait for the browser to make each request, the network is often |
| idle and underutilized. |
| |
| To improve latency, HTTP/2 introduced _server_push_, which allows the |
| server to push resources to the browser before they are explicitly |
| requested. A server often knows many of the additional resources a |
| page will need and can start pushing those resources as it responds |
| to the initial request. This allows the server to fully utilize an |
| otherwise idle network and improve page load times. |
| |
| .image h2push/serverpush.svg _ 600 |
| |
| At the protocol level, HTTP/2 server push is driven by `PUSH_PROMISE` |
| frames. A `PUSH_PROMISE` describes a request that the server predicts the |
| browser will make in the near future. As soon as the browser receives |
| a `PUSH_PROMISE`, it knows that the server will deliver the resource. |
| If the browser later discovers that it needs this resource, it will |
| wait for the push to complete rather than sending a new request. |
| This reduces the time the browser spends waiting on the network. |
| |
| * Server Push in net/http |
| |
| Go 1.8 introduced support for pushing responses from an [[https://golang.org/pkg/net/http/#Server][`http.Server`]]. |
| This feature is available if the running server is an HTTP/2 server |
| and the incoming connection uses HTTP/2. In any HTTP handler, |
| you can assert if the http.ResponseWriter supports server push by checking |
| if it implements the new [[https://golang.org/pkg/net/http/#Pusher][`http.Pusher`]] interface. |
| |
| For example, if the server knows that `app.js` will be required to |
| render the page, the handler can initiate a push if `http.Pusher` |
| is available: |
| |
| .code h2push/pusher.go /START/,/END/ |
| |
| The Push call creates a synthetic request for `/app.js`, |
| synthesizes that request into a `PUSH_PROMISE` frame, then forwards |
| the synthetic request to the server's request handler, which will |
| generate the pushed response. The second argument to Push specifies |
| additional headers to include in the `PUSH_PROMISE`. For example, |
| if the response to `/app.js` varies on Accept-Encoding, |
| then the `PUSH_PROMISE` should include an Accept-Encoding value: |
| |
| .code h2push/pusher.go /START1/,/END1/ |
| |
| A fully working example is available at: |
| |
| $ go get golang.org/x/blog/content/h2push/server |
| |
| |
| If you run the server and load [[https://localhost:8080][https://localhost:8080]], |
| your browser's developer tools should show that `app.js` and |
| `style.css` were pushed by the server. |
| |
| .image h2push/networktimeline.png _ 605 |
| |
| * Start Your Pushes Before You Respond |
| |
| It's a good idea to call the Push method before sending any bytes |
| of the response. Otherwise it is possible to accidentally generate |
| duplicate responses. For example, suppose you write part of an HTML |
| response: |
| |
| |
| <html> |
| <head> |
| <link rel="stylesheet" href="a.css">... |
| |
| |
| Then you call Push("a.css", nil). The browser may parse this fragment |
| of HTML before it receives your PUSH_PROMISE, in which case the browser |
| will send a request for `a.css` in addition to receiving your |
| `PUSH_PROMISE`. Now the server will generate two responses for `a.css`. |
| Calling Push before writing the response avoids this possibility entirely. |
| |
| * When To Use Server Push |
| |
| Consider using server push any time your network link is idle. |
| Just finished sending the HTML for your web app? Don't waste time waiting, |
| start pushing the resources your client will need. Are you inlining |
| resources into your HTML file to reduce latency? Instead of inlining, |
| try pushing. Redirects are another good time to use push because there |
| is almost always a wasted round trip while the client follows the redirect. |
| There are many possible scenarios for using push -- we are only getting started. |
| |
| We would be remiss if we did not mention a few caveats. First, you can only |
| push resources your server is authoritative for -- this means you cannot |
| push resources that are hosted on third-party servers or CDNs. Second, |
| don't push resources unless you are confident they are actually needed |
| by the client, otherwise your push wastes bandwidth. A corollary is to |
| avoid pushing resources when it's likely that the client already has |
| those resources cached. Third, the naive approach of pushing all |
| resources on your page often makes performance worse. When in doubt, measure. |
| |
| The following links make for good supplemental reading: |
| |
| - [[https://calendar.perfplanet.com/2016/http2-push-the-details/][HTTP/2 Push: The Details]] |
| - [[https://www.igvita.com/2013/06/12/innovating-with-http-2.0-server-push/][Innovating with HTTP/2 Server Push]] |
| - [[https://github.com/h2o/h2o/issues/421][Cache-Aware Server Push in H2O]] |
| - [[https://developers.google.com/web/fundamentals/performance/prpl-pattern/][The PRPL Pattern]] |
| - [[https://docs.google.com/document/d/1K0NykTXBbbbTlv60t5MyJvXjqKGsCVNYHyLEXIxYMv0][Rules of Thumb for HTTP/2 Push]] |
| - [[https://tools.ietf.org/html/rfc7540#section-8.2][Server Push in the HTTP/2 spec]] |
| |
| * Conclusion |
| |
| With Go 1.8, the standard library provides out-of-the-box support for HTTP/2 |
| Server Push, giving you more flexibility to optimize your web applications. |
| |
| Go to our [[https://http2.golang.org/serverpush][HTTP/2 Server Push demo]] |
| page to see it in action. |