diff --git a/rfc-115-enabling-http2-on-govuk.md b/rfc-115-enabling-http2-on-govuk.md new file mode 100644 index 00000000..48637f3a --- /dev/null +++ b/rfc-115-enabling-http2-on-govuk.md @@ -0,0 +1,131 @@ +# Enabling HTTP/2 on GOV.UK + +## Deadline for comments +27th January 2020 (2 weeks). + +## Summary +Back in November 2018 we trialed the use of HTTP/2 on GOV.UK. According to quite a few sources, enabling HTTP/2 should improve web performance for users by introducing technology like multiplexed streams, HPACK header compression and stream prioritisation. Unfortunately it turned out that from our synthetic web performance testing it actually slowed the site down in many instances. + +![Results from testing HTTP/1.1 vs HTTP/2.](rfc-115/cold-cache-summary.png) + +We tested 5 different page types, on multiple devices and connection speeds and examined the following performance metrics to come up with a result: + +* First visual change +* Visually complete 95% +* Last visual change +* Speed index +* Load time (fully loaded) + +And for Lighthouse reports these metrics were examined: + +* First Contentful Paint +* First Meaningful Paint +* Speed Index +* First CPU Idle +* Time to Interactive + +The RFC below discusses the problems with our current setup and suggests possible solutions. + +## Problems +### 1 - Sub Resource Integrity (SRI) + +On GOV.UK we are using Subresource Integrity for all our CSS and JavaScript assets coming from the assets domain. The [SRI specification](https://www.w3.org/TR/SRI/#cross-origin-data-leakage) requires that the `crossorigin` attribute is set to `anonymous` to be used with SRI resources for security reasons. This is because of data leakage from credentialed TCP connections. This requirement is forcing the browser to open a second TCP connection in 'anonymous mode' so it can download the CSS / JS from the assets domain. In doing so this is adding 100's of milliseconds of delay to the page rendering timeline. This occurs even when using the [`preconnect`](https://www.w3.org/TR/resource-hints/#fetching-the-resource-hint-link) resource hint, a browser feature intended to help fix this issue. + +This performance issue is occurring in both HTTP/1.1 and HTTP/2 as seen in the WebPageTest connection view waterfalls below: + +#### HTTP/1.1 +![The connection view can tell you a lot about how your connections are being utilised. Focus on each one in turn and see how much of the row is empty. This will show you the wasted time on each connection.](rfc-115/connection-view-annotated.png) + +Here we can see 13 TCP connections being opened (6 'credentialed', 6 'anonymous', 1 third-party to Google Analytics). If we weren't using SRI, we could reduce this requirement down to 8 (6 'credentialed', 1 'anonymous' for fonts, 1 third-party to Google Analytics). This is the first step in improving web performance for GOV.UK users on HTTP/1.1. + +#### HTTP/2 +Below you can see the delay in the waterfall while the 2nd `anonymous` TCP connection is established: + +![The delay seen in the HTTP/2 waterfall chart](rfc-115/h2-dns-annotated.png) + +And this is what it could look like if we were to remove SRI: + +![](rfc-115/the-impact-annotated.png) + +In the example test above on a Nexus 5 device under 3G connection speeds, we could bring the request of the CSS & JS files forwards by ~750 ms. This should speed up the whole waterfall and turn the results from the summary list above from red to green in favour of HTTP/2. + +This is achieved through the use of HTTP/2 connection coalescing, which can be seen in action on GOV.UK from our trial below: + +![](rfc-115/connection-view.png) + +The coalesced connection is under-utilised if 'anonymous mode' is used on our static assets. There's also an impact from the fact that [TCP Slow Start](https://en.wikipedia.org/wiki/TCP_congestion_control#Slow_start) is in action on the delayed `anonymous` connection, meaning assets will download slower than they could via the single coalesced connection. So we are in the following situation: the connection used to download the HTML isn't utilised fully (by this point it will already be up to speed), and the `anonymous` connection downloading the critical CSS has been delayed, so it isn't up to speed yet. For best performance we should be utilising the connection that has already been established via the HTML download, and use it for other critical page assets (CSS/JS). + +### 2 - Assets served with `Access-Control-Allow-Origin: *` +My initial thinking was that we could quickly switch the `crossorigin` attribute from `anonymous` to `use-credentials` for our static assets (CSS/JS). Unfortunately on examining the [Fetch specification](https://fetch.spec.whatwg.org/) there's [information in the table (5th row down)](https://fetch.spec.whatwg.org/#cors-protocol-and-credentials) that states: + +> `Access-Control-Expose-Headers`, `Access-Control-Allow-Methods`, and `Access-Control-Allow-Headers` response headers can only use `*` as value when request’s credentials mode is not "include". + +Basically, the use of the wildcard (`*`) isn't allowed on a credentialed connection (`use-credentials`). And if it is used, the browser will block any requests and raise an error message the that looks like this: + +> Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ‘https://assets.example.com/script.js’. (Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’). + +We are currently [serving all our assets](https://github.com/alphagov/govuk-puppet/blob/962ea899e9c6778fe91e80074346912bd4314b10/modules/router/templates/assets_origin.conf.erb#L36-L38) from the `assets` domain with the `Access-Control-Allow-Origin: *` header, which is blocking us from making this change. The reason why we are serving assets like this is because we are using SRI and they need the `crossorigin` attribute for it to work. + +The Heroku applications (e.g. https://government-frontend.herokuapp.com) that are created are a legitimate reason for using this header when `crossorigin` is present. If we were to remove the `Access-Control-Allow-Origin: *` header then the JS and fonts (due to their [unique CORS requirements](https://www.w3.org/TR/css-fonts-3/#font-fetching-requirements)) on the Heroku previews would break. According to the `Access-Control-Allow-Origin` [documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin), there are only 3 valid values for this header: + +- `*` +- `` +- `null` + +The `` value is very important. As this is where we would ideally like to specify: `*.gov.uk; *.herokuapp.com`. Unfortunately this header only accepts a **single** origin, and it **doesn't** recognise wildcard values. So we are in a situation where we need to use `*` (for SRI) and a specific origin value (e.g `https://government-frontend.herokuapp.com` for every unique Heroku app URL, including individual PR's) at the same time. There's the possibility that this can be done by adjusting the [VCL config on the CDN](https://www.fastly.com/blog/caching-cors), but that then opens us out to more potential complications as listed in @kevindew's comment [here](https://github.com/alphagov/govuk-rfcs/pull/115#discussion_r366510706). So this could complicate both the CDN configuration and local development if not properly investigated. + + +### 3 - The assets domain +Domain sharding for static assets is an anti-pattern under HTTP/2, and our current HTTP/1.1 setup isn't optimal for performance either. If our static assets weren't being served including SRI we would be able to remove the `crossorigin` and `integrity` attributes from the `