-
Notifications
You must be signed in to change notification settings - Fork 332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Define a cache for H2 server push & friends #354
Comments
I agree with your thinking here. On document-insertion Then before step 3 of https://fetch.spec.whatwg.org/#http-fetch: (with lots of hand-waving)
Can these be easily associated with a fetch group? I thought a single H2 connection would be used for multiple pages. |
In the above I'm trying to ensure that the preload cache is used even if the request is still in-progress. |
@jakearchibald a H2 server push is part of an HTTP response to an HTTP request. So it can only exist as part of a single request-response stream and not on its own. So therefore you should always be able to associate the push promises with a fetch group. |
You're right, the PUSH_PROMISE has a stream ID for itself along with the stream ID for its data. In that case I agree that it should match I believe we don't allow pushed items into the HTTP cache until they're "used". This doesn't make a whole lot of sense to me, but maybe @igrigorik can explain why. |
That's not correct in the case of @annevk, please make sure that you talk to some of the network team folks at Mozilla about this; I'm not going to have the bandwidth to be involved in this conversation... |
It'd be a lot simpler if |
https://groups.google.com/a/chromium.org/forum/#!topic/loading-dev/GoONR_xSGJ4 In Chrome, pushed resources don't enter the http cache unless they're matched up to a future request. As a developer, this is pretty unexpected, but I'm keen to hear from other vendors. |
When this was discussed during the HTTP workshop vendors seemed to be open to changing this. After all, if you want to fill up the HTTP cache there's plenty of ways to do so already. So not letting H2 server push near it is weird. The uncacheables would get the lifetime of the fetch group, per OP. |
@annevk I'm curious what's intended by the fetch group semantics, as I don't believe that's something implemented in Chrome (nor easily implemented). The closest parallel I can think is if it's meant to be the abstraction layer detailing how our renderer process works (and the relationship to the renderer-side memory cache), but if that's the case, then it certainly is not part of our implementation or notion of Fetch, nor would I be keen (nor would it be possible) to hang more stuff off it. I think I'd disagree with @jakearchibald on the relationship between PUSH and the cache being weird, and it may simply have to do with the choice of verb attached to it implying a semantic meaning unmet by implementations; that is, had it been called, say, OPPORTUNISTIC_RESOURCE, would that have made it clearer or not? I'm not aware of any implementation letting H/2 into the cache, and while that may be something to discuss, I absolutely want to make sure we don't entangle it with the notion of fetch groups unless/until we have a solid understanding of what they're trying to describe or prescribe. I'm not sure if this is exactly the right bug to hash it out, and I suspect I'd need to rope in a few colleagues to make sure we're accurately describing it, but for now, I'd like to separate out protocol-handling discussions (e.g. H/2) from "Fetch/Web Platform" level discussions (e.g. prefetch, preload; whether or not that's a fair characterization I'm not sure, but that's certainly reflected in how Chrome has implemented them and architected around) |
A fetch group is something that manages all the fetches for a document or worker and has a similar lifecycle. The precise details are still a little unclear, but we need something like that to define what happens when navigating and such. I think it would be fine to discuss H2 server push separately from prefetch and friends. We could first try to sort out the latter and then see how to improve the former. There's also an open JavaScript API question around H2 server push that at some point will come to a head and we have better figured out the layering by then. |
Comment on "fetch group", sorry if its off topic: One thing we have not had to deal with in FF so far are fetches in the same fetch group (we call it a LoadGroup) that are triggered from different processes. We will have to figure this out as we move to a more multi-process architecture. But this might explain some differences between FF and chrome today. Perhaps we will end up looking more like chrome here for pragmatic reasons. If we want to add functionality to fetch group we need to think about how they work across process boundaries. I guess its more of an implementation concern, but it will restrict what can be realized from the spec. |
I've typed this a few times over the past weeks, and deleted it, so it's probably still a bad idea. The "list of available images" has a lot of similarities to the preload cache. The differences are:
We could try and merge these. |
I think the intent is for the preload cache to sit with the fetch group, so preload requests go through the SW. H2 cache is currently at the network level in Chrome though. |
Preload response's are one of several responses that interact with the (yet to be formally defined) response cache. As such, the logic for both populating and querying said cache should live in Fetch. - Updated issue text as a warning, with some context and pointer to the discussion in Fetch. - Removed "match a preloaded response" definition: this should be handled transparently by Fetch, as one of the first steps in its main fetch algorithm. Fetch issue where we should continue this discussion: whatwg/fetch#354 Closes #30.
* response cache and matching lives in Fetch Preload response's are one of several responses that interact with the (yet to be formally defined) response cache. As such, the logic for both populating and querying said cache should live in Fetch. - Updated issue text as a warning, with some context and pointer to the discussion in Fetch. - Removed "match a preloaded response" definition: this should be handled transparently by Fetch, as one of the first steps in its main fetch algorithm. Fetch issue where we should continue this discussion: whatwg/fetch#354 Closes #30.
An interesting side-effect of Chrome's H2 push implementation: The H2 cache is stored at the connection level, so if the connection terminates, the cache is lost. Because separate connections are used for credentialed vs non-credentialed requests, you can end up with unexpected H2 cache misses. Eg: // This resource is /sw.js
addEventListener('install', event => {
event.waitUntil(async function() {
const cache = await caches.open('static-v1');
await cache.add('/script.js');
}());
}); If you push The workaround is to fetch cache.add(new Request('/script.js', {credentials: 'include'})); |
It has to work that way, just like the HTTP cache (see #307) otherwise you have leaks. |
@jakearchibald - Yup, see https://bugs.chromium.org/p/chromium/issues/detail?id=669515 @annevk - If it has to work this way, it means that no-credential fetches (fonts, ES6 modules, and SW fetched resources in Jake's example) can never be reclaimed from push, and therefore are inherently slower than their credentialed counter-parts :/ |
Right, that's a known problem without a solution (see #341). |
Yoav: Why can't they be claimed? Push them in the non-credentialed
connection.
|
@sleevi - The non-credentialed connection often doesn't get created until later on, while H2 push is most effective before the HTML ever reaches the browser, and shortly after the HTML finished sending. |
For instance, a document's rendering may require some CSS and a font. However, you can't push the font down with the page request, because fonts are requested without credentials and pages are. But the lack of fetch control in CSS is probably the issue here. |
The above is only an issue if the font is on another origin under the same certificate authority, as same-origin font requests get credentials, so it's not as common as I thought. |
That's true for all same-origin requests, if I read step 4 of HTTP-network-or-cache fetch and friends correctly? |
@mnot |
Huh. That's weird, but ok. |
Back to the issue of what the purpose behind NOT using the HTTP cache for H2 PUSHed responses -- IIRC (and this is hazy), one concern was that on sites that represent more than one party (e.g., a shared web host), a hostile user could push content into the cache under another user's URLs. However, as was pointed out at the workshop, our malicious user could easily circumvent that by just referencing them from the page that's loaded from the attacking URL; they'll be "used" by that page, and then promoted into the HTTP cache. See also: http://httpwg.org/specs/rfc7540.html#rfc.section.10.4 I think that the browser implementers of H2 did things this way out of an abundance of caution, but the feeling at the workshop was that we could probably move past this now. @mcmanus @martinthomson anything to add? |
@mnot I wouldn't say that they're equivalent.
The loading of a resource (that is, the attacking page is not same-origin
as the receiving page) results in a different request flow than a push
(even a push associated with a specific stream serving the attacking page's
content). This is because any step that could block a network load as part
of processing https://fetch.spec.whatwg.org/#http-fetch becomes an
observable difference for non-same origin pushes. Since CORS is the most
likely thing to cause a network error event, this makes 3P pushes almost
certainly SOP violations.
Even if it was limited to same-origin pushes, there's the question of
whatever a specific implementation does as part of general processing (e.g.
safe browsing checks, image blocking checks, etc). Whenever something is in
the cache, it will have gone through all of those checks today. If any of
those checks rely on the context of understanding what page content
'initiated' the request - whether they be UA specific or even the common
checks in fetch that rely on the 'type' of the fetch - then these end up
with observable differences, both to the page and to the user (who may not
want such requests stored to disk).
I'm not sure if you were trying to suggest that was an "overabundance of
caution", but I think that's the set of concerns of why I think going in
any direction to allow H2 PUSH to push to the HTTP Cache will involve some
degree of discussion around the Fetch spec and tests.
|
(I'm really fuzzy on which origins we're talking about being the "same" here. There is a page origin, the request origin, and the origin of the pushed resource. All are potentially relevant in this context.) I'm looking at fetch and it doesn't appear to be that there is any problem. I agree that there are many reasons for withholding a request, but they can all be reduced simply by observing two things:
I appreciate the caution, and the reasons, and the need to make some changes to accommodate the above model (if you accept that it is valid). However, I don't think that those reasons are strong enough to say that you categorically don't cache pushed resources. |
On Sun, Feb 26, 2017 at 4:07 PM, Martin Thomson ***@***.***> wrote:
(I'm really fuzzy on which origins we're talking about being the "same"
here. There is a page origin, the request origin, and the origin of the
pushed resource. All are potentially relevant in this context.)
I'm not sure how best to clarify the confusion, because there's only one
origin - it's a scheme/host/port tuple. It's the processing model that
varies, and that is the point. The coalescing of HTTP/2 does not mean that
you can skip the processing model of Fetch or CORS - as to do so would
undermine the security properties and principles that the SOP is designed
to protect.
I'm looking at fetch and it doesn't appear to be that there is any problem.
We should work to figure out why, given the above explanation. It's unclear
whether you disagree with my observations regarding the interaction or
whether I didn't communicate them well.
I agree that there are many reasons for withholding a request, but they
can all be reduced simply by observing two things:
1.
Sites can cause the browser to make a request. This is no different to
generating a push promise.
I tried to explain why this observation is incorrect. I'm not sure if the
fuzziness above made that less obvious, but I'm hopeful we can figure out
where the confusion is. There are a number of notable differences here, in
that there are a number of ways in which sites cannot cause the browser to
make certain types of requests, whereas PUSH promises would allow them to
make that. That's the problem - and a security risk.
1.
The existence of a response does not need to mean that the response
needs to be used. Thus, all the blocking, service workers, and other
conditions can easily apply before consuming the response. Worst case, the
request needed a preflight; so send the preflight and hope that that was
pushed as well.
I'm not sure I agree with this, on two grounds:
1. You cannot apply these easily, because of the issues I've already
highlighted in that an initiation of fetch has a context (in the page and
origin) that exceeds the available information in a PUSH PROMISE. Ergo, you
cannot map the information from a PUSH PROMISE into an equivalent framing,
even though you have an associated URL.
2. I disagree that preflight is the "worst case", due to the fact that any
form of cross-origin push is already a-priori cross-origin. Therefore, I
assert that the need for a preflight is arguably the common case for any
form of cross-origin push.
These two concerns - and the issues I highlighted - hopefully demonstrate
why you it's neither an 'easy' matter nor a compliant manner to allow a
PUSH into the HTTP cache.
I appreciate the caution, and the reasons, and the need to make some
changes to accommodate the above model (if you accept that it is valid).
However, I don't think that those reasons are strong enough to say that you
categorically don't cache pushed resources.
I'm not sure how we can agree on the above item but disagree here. As it
stands, you categorically cannot cache pushed resources safely. I think
we're both in agreement that changes need to be made, but I'm suggesting
that as it stands, it is unsafe to enter a pushed item into the cache,
because it violates the processing model defined in the Fetch spec in
observable ways, both for the end-user and for the page author.
I think we're in agreement that it is possible to imagine an outcome where
we do allow this, by refining the specs and, in some cases, outright
preventing it, but I think the point being is that this is all work that
needs to be done, has not been done, and has non-trivial security and
privacy impact, ergo, a UA cannot simply allow this for anything but
same-origin fetches in limited cases.
|
(Ahh, email replies in github remain a real challenge.)
I admit to not understanding your response at all. Let me try to explain a little more about what I was talking about. When a site makes a request (maybe by invoking If this is what you refer to when you talk about "an initiation of fetch has a context (in the page and origin) that exceeds the available information in a PUSH PROMISE", then we're probably just in agreement on the need to walk through the wrinkles.
I didn't mean to imply skipping any checks. The opposite in fact. I meant to observe that you don't need to skip any checks at all and that by doing so you achieve a system with similar - though maybe not identical - properties to one where sites can request that the browser fetch things (i.e., the one we have today). |
It's not what I meant, so at least now we've found the source of confusion. Start with the algorithm described in https://fetch.spec.whatwg.org/#http-fetch , and work through every place where "return a NetworkError" is specified. Then look at what conditions cause that. You will see there is more context than 'just' the URL. For example, the algorithm in https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-nosniff? is dependent on https://fetch.spec.whatwg.org/#concept-request-type which is dependent on the element or context in the page that caused the fetch. https://fetch.spec.whatwg.org/#concept-request-destination is perhaps an even clearer highlighting of the variations in the processing model that affect things during Fetch. As it stands in the world today, where user agents do not allow direct pushes into the HTTP cache, there are a number of ways in which a request from |
@sleevi - I wasn't talking about coalesced origins; for the moment, let's figure this out for same-origin (while still acknowledging that coalescing will raise its head at some point). Thanks for giving concrete examples. WRT WRT I went through the rest of the Fetch spec as you asked, and didn't see any immediate problems. Can you point out any more? Happy to admit I'm missing something here, because I know you have a deeper understanding of the internals here. I totally get that browsers may have made some optimisations in their implementations by assuming that things that successfully get into the HTTP cache have specific attributes. I can even see that from an implementation perspective, it makes sense to create a separate cache for pushes or to taint them because of historical implementation decisions. I'm just not yet seeing a reason to call it a different kind of cache in the specs yet, when the specs appear to already be written in a manner where It Just Works, more or less. |
@mnot I totally appreciate your perspective coming at it from the view of a server operator, because I think it's important to distinguish between what's observable to the client, what's observable to the server, what's observable to intermediates, and what's observable to the user. My premise is that any observable differences should be spec'd - whether that appears in HTTP (and related) in the side of IETF, or in Fetch (and related) from the perspective of the client here - and my focus has been on observable behaviours emitted from the client or affecting the user. This is mostly out of selfish interest - I can influence client behaviour, but I can't help server behaviours and how they cache or if they cache appropriately. For same-origin fetches, I agree with you that it's unlikely to be problematic from either a security perspective or a network-observable perspective. This is why Chrome is exploring implementation options to better optimize the H/2 experience, and why there is still a lot of discussion around rel=preload and the H/2 cache. In discussing the processing model, the following stand out for same-origin fetches:
This is why I think that, generally speaking, we (Chrome folks) have mostly convinced ourselves that same-origin pushes to the cache are probably OK. However, there's a big looming question mark on whether it's the right thing for the user - when it's done right, it can reduce latency, but when it's done wrong, it can waste users' bandwidth and disk space. Further, as you've alluded to, from an implementation perspective, there's a lot of complexity - perhaps intractably so, given the API surface exposed - that doesn't have a lot of people jumping for joy at supporting either same-origin or cross-origin. An example of this is the intersection with the webRequest API and how that affects resource consumption - e.g. imagine an advertiser pushing assets to the client when they're running an ad-blocker. The fact is, as far as implementations go, it is a different cache, and that's unlikely to change in the near future precisely because of the various complexities. So to the extent @annevk likes specs to reflect the real world, I'm wholly supportive of it. However, if other implementations are doing things differently, then it's something to explore and evaluate, but better to have the spec reflect truth. |
@sleevi thanks for that. The question of whether it's good for the user came up often during the development of H2, and IIRC was always answered by Chrome folks as "servers can already push things using inlining, so that's not a valid argument against Server Push." Forgive me if I enjoy the irony a bit here :) It seems to me like server push adds complexity to webrequest regardless of how many caches you model this as, but of course I'm not as familiar with the details. I'd really like to hear from other implementers too. |
We have several features that interact with some cache that is not really defined:
I think the approximate semantics we want are HTTP cache semantics. However, resources that cannot be cached should only survive the lifetime of the fetch group and can only be fetched within the scope of that fetch group (once?).
The text was updated successfully, but these errors were encountered: