httpwg / http-core

Core HTTP Specifications
https://httpwg.org/http-core/
467 stars 43 forks source link

Caching: Vary, Conneg, and choosing the latest response #1109

Open ethanresnick opened 1 year ago

ethanresnick commented 1 year ago

Hello all! I'm making my way through RFC 9111 and am finding a few bits to be potentially contradictory. In some cases, I think I understand the intention and the issues seem merely editorial; in other cases, I'm genuinely confused about what the semantics are supposed to be.

First, a simple/pedantic editorial issue, with this paragraph:

When more than one suitable response is stored, a cache MUST use the most recent one (as determined by the Date header field). It can also forward the request with "Cache-Control: max-age=0" or "Cache-Control: no-cache" to disambiguate which response to use.

The second sentence above seems weird:

  1. If the cache MUST use the most recent response (that it already has stored), does that mean that, if it's forwarding the request, that the resulting response my only be used for handling future requests, not the current one? That doesn't seem like the intention — it seems like the idea is to allow the cache to not use the newest stored response, and forward the request instead — but that's not compatible with the MUST.

  2. If the text is updated to match the (presumed) intention, then: suppose the cache forwards the request as described and the new response comes back with a Date that's older than the Date on some of the cache's stored response. Presumably, the cache would be required to not use this new response? If that's desirable, is the second sentence actually adding anything?


Later down, when talking about Vary, there's this paragraph:

If multiple stored responses match, the cache will need to choose one to use. When a nominated request header field has a known mechanism for ranking preference (e.g., qvalues on Accept and similar request header fields), that mechanism MAY be used to choose a preferred response. If such a mechanism is not available, or leads to equally preferred responses, the most recent response (as determined by the Date header field) is chosen, as per Section 4.

First, again, there's the simple editorial issue: because section 4 says the cache MUST use the most recent response, I don't think the MAY above can actually override that? Nevertheless, it's clear that the intention is for the cache to be able to use an older response where appropriate, so the MUST can probably just be weakened accordingly.

I'm still confused about the semantics and intended use cases for the latitude given in this paragraph, though. I was able to trace this paragraph back to an old issue. That issue gives the example of a resource with two different representations that the cache has stored (jpg and png) and an incoming request with: Accept: image/jpeg;q=0.1, image/png;q=1.0. In the example, the stored png representation is a tiny bit older, but goal is to have the cache use it anyway, because of the preference for it in Accept.

My confusion stems from the fact that, presumably, the jpg and png representations were themselves stored as a result of requests that had different Accept headers (one preferring the jpg and one preferring the png), with the origin providing Vary: Accept on each response. When this new request comes in, and the cache is considering which stored response to use, its behavior is governed by this language:

When presented with a request, a cache MUST NOT reuse a stored response unless [...] request header fields nominated by the stored response (if any) match those presented

The definition of "matching" allows all these Accept values to be normalized "in a way that is known to have identical semantics, according to the header field's specification". Nevertheless, I don't see how any normalized version of the Accept header on the request that led the origin to return the jpg could possibly match this incoming request, as the incoming request's Accept header prefers the png, and therefore cannot have "identical semantics". So, it seems like, under the current language, the JPG wouldn't be a candidate response here at all, and the fact that it's marginally newer than the png is irrelevant.

Therefore, the only way I can make sense of this example is if the intention is that the cache first checks its store for candidate responses based solely on the URL (and method) portions of the cache key, and then filters those candidate responses down to ones that are in some sense "compatible" with the new, incoming request's Accept — even if the Accept on the original request that produced this stored response does not match the new request's Accept (in the sense of being identical after normalization). That seems like a giant difference to me, and would probably require modifying the language I quoted above, i.e. that "a cache MUST NOT reuse a stored response unless [...] request header fields nominated by the stored response (if any) match those presented".


Last but not least, the RFC says:

When a cache has multiple stored responses for a target URI and one or more omits the Vary header field, the cache SHOULD choose the most recent (see Section 4.2.3) stored response with a valid Vary field value.

Again, this seems to contradict the MUST about using the most recent stored response, as the most recent one could well be one that's missing a Vary header. But, even if the editorial contradiction were addressed, it seems like some guidance might be useful here on how far caches should go in returning an older response that's more likely to have the correct Vary vs using a result that may not actually satisfy the user's request (because the server's Vary was misconfigured), but is newer.

ethanresnick commented 1 year ago

cc @mnot