Photo found on Wikipedia
Hello! My name is Max Matiukhin, I work as a Badoo PHP programmer. We are constantly exploring various possibilities to speed up the work of our application and, of course, we share the most interesting finds in our blog on Habré.
The second version of the HTTP protocol promises us a lot of improvements, and one of the interesting features of HTTP / 2 is support for push. Theoretically, this feature allows you to speed up the loading of the application. Recently, Jake Archibald wrote a large article in which he analyzed the features of push implementation in various browsers, and it turned out that there are quite a few such features.
We have already published a post describing the basic HTTP / 2 Server Push functionality, and this one will be a good addition, telling how the reality with HTTP / 2 Server Push support in various browsers.
I have heard the phrase “HTTP / 2 Server Push cope with this” many times when it came to problems with loading the page, but I didn’t understand much about this topic and so I decided to get to the bottom.
HTTP / 2 Server Push turned out to be more complex and low-level than I thought, but I was really surprised by how different its interaction with various browsers is - I always thought that it was a fully developed feature, ready for use in production.
I do not want to say that HTTP / 2 Server Push is useless nonsense; I think this is a really powerful tool that will get better over time. But I no longer consider him a "silver bullet from a golden gun."
Between your page and the target server there is a whole series of caches and functions that can intercept the request:
The above model resembles the flowcharts that are used to describe Git or its characteristic features. Those who are familiar with the topic are convinced of the correctness of their knowledge, the rest are just scared. I apologize if this is the case, and I hope the following sections will help you figure it out.
Page: Hi, example.com, can I get your home page? 10:24
Server: Of course! Oh, while it's being sent, here’s another style sheet, images, javascript and json. 10:24
Page: Wow, class! 10:24
Page: I’m reading HTML, and it looks like I’ll need a sti table ... Oh, you already sent it to me, cool! 10:25
When responding to a request, the server may include additional resources, such as request headers, that the browser can match later. They are in the cache until the browser requests a resource that matches the description.
Increased productivity is due to the fact that resources are sent before the browser requests them. Theoretically, this means that the page should load faster.
That's almost everything I knew about HTTP / 2 Server Push. It sounded pretty easy, but in fact it turned out to be quite not so easy ...
HTTP / 2 Server Push is a low-level network feature: everything that uses a network stack can use it.
But any feature is useful if it is consistent and predictable. I tested HTTP / 2 Server Push on these indicators, launching resources and trying to collect them using:
fetch()
XMLHttpRequest
<link rel="stylesheet" href="…">
<script src="…">
<iframe src="…">
I also limited the download speed of body from resources that were already running, to see if browsers could match resources that were not yet running. A small test suite is available on GitHub .
Edge, when using fetch (), XMLHttpRequest <iframe>
did not retrieve the item from the push cache ( description of the problem ).
Safari behaved in a strange way. It is impossible to predict whether he will use the push cache or not. Safari relies on the OS X network stack, whose source code is closed, but I believe that some of the bugs are specific to Safari. It seems that it opens up too many connections, which ultimately distributed resources run. This means that you only get into the cache if the request was lucky to use the same connection (this is beyond my comprehension ( problem description )).
All browsers (except Safari, when it is odd) will use matching running elements, even if they are still in the download process, and this is very good.
Unfortunately, only Chrome supports developer tools, thanks to which on the network panel you can find out which items were extracted from the push cache.
If the browser does not retrieve an item from the push cache, the speed will be less than if no push had ever been applied.
Technology support in Edge leaves much to be desired, but we know exactly what methods do not work with push-caches. You can define a User-Agent client and push only those resources for which it precisely supports push-cache.
Safari's behavior is not deterministic, so you can hardly cover it with crutches. You can simply disable HTTP / 2 Server Push for Safari users based on User-Agent.
When using the HTTP cache, the element should have something like max-age so that the browser can use it without re-checking on the server ( post about caching headers ). To use HTTP / 2 Server Push this is not required - when comparing elements, their “freshness” is not checked.
All browsers behave the same.
The performance of some single-page applications may suffer due to the fact that their rendering is blocked not only by JS, but also by some data (JSON, for example), which JS starts to extract immediately as it executes. The best solution is server rendering. However, if this is not possible, you can push JS and JSON along with the page.
Given the aforementioned Edge and Safari problems, embedding JSON is a more reliable solution.
The launched elements are bound to an HTTP / 2 connection, that is, the browser will use them only if no response is received from the image cache, the preload cache, the service worker cache, and the HTTP cache before.
All browsers behave the same.
For information: let's say in the HTTP cache you have an item that is “new” in accordance with its max-age, and you push a newer item. Then the latter will be ignored in favor of the first (unless the API for any reason bypasses the HTTP cache). Just know about it.
Knowing that cached items are related to an HTTP connection helped me understand the other behavioral features I encountered. For example…
The push cache is connected to an HTTP / 2 connection, so you will lose it if the connection is broken. This happens even if the fired resource is actively cached.
The push cache is outside the HTTP cache, so elements are not included in the HTTP cache until the browser requests them. At this moment, they are retrieved from the push cache and are transferred via HTTP cache, service worker, etc. to the page.
If the user has an unstable connection, then you can successfully push something, but the connection will be interrupted before the page receives the data. We'll have to establish a new connection and reload the resource.
All browsers behave the same.
Do not rely on the fact that the items will be long in the cache. Push is best used for urgent things, such that where it doesn’t take long to push and use.
Each connection has its own push cache. But since one connection can be used by several pages, they can also share one push cache.
In practice, this means that if you push a resource along with a response related to navigation (for example, an HTML page), it will not only be available for this page (I will use the term “pages” throughout the post, but in Actually, it means other contexts that can extract resources, for example, workers).
It seems that Edge for each tab uses a new connection ( description of the problem ).
Safari unnecessarily creates multiple connections to the same source. And I'm pretty sure that this is the reason for his strange behavior ( description of the problem ).
Keep this in mind when pushing things like JSON data along with the page — you can't rely on the same page to catch them.
But this behavior can turn into an advantage, since the resources that you use with the page can be picked up by the requests that are made by the installation service worker.
Edge behaves suboptimally, but nothing to worry about. Now, if it had the support of a service worker, then this could be a problem.
And again, I would not advise Safari users to push resources.
The term “authorization data” will appear more than once in this post. It refers to the data sent by the browser used to identify a specific user. These are usually cookies, but they can also be Basic HTTP authentication identifiers and connection level identifiers, such as client certificates.
If you compare the HTTP / 2-connection with a phone call, then as soon as you introduce yourself, the call will no longer be anonymous, and this applies to what you said on the phone before you called yourself. For confidentiality purposes, “anonymous” requests set up a separate “call” to the browser.
However, due to the fact that the push cache is connected to the connection, you can lose cached elements when executing requests without credentials (non-credentialed request). For example, if you push a resource along with a page (request with credentials) and then retrieve it (fetch ()) (without credentials), a new connection will be established, and the pushed element will be lost.
If a cross-origin stylesheet (with credentials) fills a certain font, then when the browser requests it (without credentials), the font will be lost in the push cache.
Make sure your requests use the same authorization method. In most cases, this means that requests contain credentials, since the page request is always executed with authorization data.
To get credentials, use:
fetch(url, {credentials: include});
You cannot add authorization data to a font request using multiple sources, but you can remove it from the style sheet:
<link rel="stylesheet" href="…" crossorigin>
This means that the request for both the style sheet and the font will follow the same connection. But if the stylesheet also applies background images, then such requests always come with authorization data, so you can get another connection as a result. The only solution is to use a service worker who can configure each request.
The developers claim that requests without credentials improve performance, since they do not need to send cookies. However, it is necessary to compare them with a much higher cost to establish a new connection. In addition, HTTP / 2 Sever Push can compress headers repeated in different requests, so cookies are not really a problem.
Edge is the only browser that does not follow the rules. It allows connection sharing using queries that contain or do not contain data for authorization. Here I missed the usual row of browser icons because I wanted the specifications to be changed.
If a page makes a request to its source without data for authorization, then it hardly makes sense to establish a separate connection. The request is initiated by a resource containing authorization data that can be added to an “anonymous” request via a URL (this is called ambient authority).
I’m not quite sure about other cases, but if you make requests to the same server with or without authorization data, anonymity is lost due to browser “fingerprints”. If you want to study this issue in more detail, read the discussion on GitHub , the Mozilla mailing list, and the bug report in Firefox .
If the browser used an element stored in the push cache, then the latter is removed from the cache. It may be in the HTTP cache (depending on header caching ), but it will no longer be in the push cache.
Here Safari suffers from race conditions. If the resource is extracted several times while it is being fired, then it will receive the fired element several times ( description of the problem ). If the resource is extracted twice after the push has ended, then it behaves correctly: for the first time it will be returned from the push cache, and in the second it will not be.
If you decide to push something through to Safari users, then be prepared for an error when pushing no-cache resources (for example, JSON data). Along with the answer, a random ID can be sent. And, if you received the same ID twice, know that an error has occurred. In this case, wait a second, and then try again.
In any case, use caching headers or a service worker to cache running resources after they are retrieved if caching is undesirable (for example, one-time JSON samples).
When you push content, you do it without much interaction with the client, which means you can push through what the browser already has in one of its caches. The HTTP / 2 specification allows the browser to interrupt incoming traffic using the CANCEL
or REFUSED_STREAM
code to avoid loss of bandwidth.
The specification is not strict here, so my judgment is based on what is useful for developers.
Chrome rejects push if the item is already contained in the push cache. Rejection is done using PROTOCOL_ERROR
, not CANCEL
or REFUSED_STREAM
, but this is not so important (description of the problem). Unfortunately, it does not reject items that are already contained in the HTTP cache. It seems that now this problem is almost solved, but I could not make sure of it ( description of the problem ).
Safari rejects push if the item is already contained in the push cache, but only if it is “new” according to the headers in the cache (for example, max-age) until the user refreshes the page. Safari is different from Chrome, but I don’t think it’s “wrong.” Unfortunately, like Chrome, it does not reject elements that are already in the HTTP cache ( description of the problem ).
Firefox cancels push if the item is already contained in the push cache, but then it deletes the item and leaves nothing! This makes it unreliable and complicates its protection ( description of the problem ). Like the above browsers, Firefox does not reject elements that are already contained in the HTTP cache ( description of the problem ).
Edge does not reject push if the element is already contained in the push cache, but does so if the element is in the HTTP cache.
Unfortunately, even with perfect browser support, bandwidth and server I / O resources will be wasted before you get a cancellation message. Cache Digest can be the solution to the problem: the extension tells the server that it has already been cached.
In addition, using a cookie, you can track whether cached resources are already pushed through to the user. However, elements may disappear from the HTTP cache at the whim of the browser, while the cookie will be preserved. So the presence of a cookie does not mean that the elements are still contained in the user's cache.
We have already seen that “novelty” is not taken into account when it comes to matching items in the push cache (this is how no-store and no-cache are matched), and other matching mechanisms should be used instead. I tested POST and Vary requests: Cookie.
Update: The specification says that push requests "MUST be cacheable , MUST be safe and MUST NOT contain the body of the request." I missed these definitions first. POST requests do not fall under the definition of "secure", so browsers must reject them.
Chrome accepts POST push streams, but does not use them ( description of the problem ). It also ignores the Vary header when matching loaded items ( description of the problem ), although the problem suggests that it works when using QUIC.
Firefox rejects POST push streams, but also ignores the Vary header when matching launched elements ( problem description ).
Safari , Chrome, POST push-, ( ). , Vary.
, , Safari, Vary . , : JSON, , , push-JSON , .
, , ID . , ( ).
Chrome . (Clear-Site-Data header), . push-, HTTP/2-.
developers.google.com/web , , android.com . , HTTP. , android.com, NATIVE SUX – PWA RULEZ
, omic Sans .
, – Android. … Android: , .
-, , . , , «».
developers.google.com, , Google, android.com.
, , , android.com, DNS , IP-, developers.google.com, , push-.
ORIGIN frame , , , : «, - android.com, . DNS». , Firefox Nightly.
CDN - , , . . , ( , ) HTTP/2 Server Push. :
(tenant) , , ( HTTP/2 ).
.
Chrome , . , IP-, . Chrome ORIGIN frame.
Safari , , , - . Safari ORIGIN frame.
Firefox push . Safari, . , . Firefox Nightly ORIGIN frame.
Edge push . , . Edge ORIGIN frame.
, , ORIGIN frame. DNS, .
, push , , , . User-Agent push .
, HTML:
<link rel="preload" href="https://fonts.example.com/font.woff2" as="font" crossorigin type="font/woff2" >
:
Link: <https://fonts.example.com/font.woff2>; rel=preload; as=font; crossorigin; type=font/woff2
• href
– URL .
as
– : CSP-.crossorigin
– . , CORS-. CORS- , crossorigin = «use-credentials»
.type
– . , MIME- ., . HTTP/2 Server Push:
.
, , -, HTTP, HTTP/2 ( ).
( ), ( - HTTP), . , .
, , .
, , . - – .
Chrome API. , fetch()
. XHR , ( ).
Safari Technology Preview . fetch()
, XHR
( ).
, HTTP/2 Server Push, , . . , . , , .
HTTP/2 Server Push. , , , , , , - . , , , , push, .
HTTP/2 Server Push . , , (), (), CSS. , - , , .
«» , . , , CSS , CSS, , .
(- - ), , , , , ; , .
HTTP/2 Server Push, , . HTTP/2 Server Push ; , .
Source: https://habr.com/ru/post/331216/
All Articles