Subject: Re: Server Push and Caching

From: David Witherspoon <[email-masked]>
Date: Wed, 24 May 2017 17:27:53 +0000

>  The only native HTTP mechanism for cache invalidation is described in RFC7234, Section 4.4:

I believe this is not entirely correct.  RFC7540 Section 8.2.1: 

   Server push is semantically equivalent to a server responding to a
   request; however, in this case, that request is also sent by the
   server, as a PUSH_PROMISE frame.

Meaning all well-specified semantics of an HTTP request are available to a Push Promise/client-initiated request.  

RFC7234 Section 5.2.1.4 defines one, (request cache-directive, no-cache):

   The “no-cache” request directive indicates that a cache MUST NOT use
   a stored response to satisfy the request without successful
   validation on the origin server.

It would be semantically incorrect to cancel a server-initiated request with a no-cache request directive for the sole reason that it is already has a cached response.  In fact, the premise that a server-initiated request with a no-cache request directive can already have a cached response violates semantic equivalency.  Any request, client or server initiated, with a no-cache request directive can not by RFC have a validated response in the cache.

Assuming the Push Promise goes through (client still may cancel the Push Promise for any reason), this clearly allows for overwriting an existing cached response, specifically replacing a cache entry.

I’m uncertain if there is a response that could complete a push promise in a way to remove a cache entry.  But by SPEC, it seems any server-initiated request (or client initiated request) with a no-cache request directive should invalidate the existing cache entry as soon as that request reaches the caching layer.  Arguably, replacement is a better use case anyways.

Related conversation: https://bugs.chromium.org/p/chromium/issues/detail?id=232040#c75 <https://bugs.chromium.org/p/chromium/issues/detail?id=232040#c75>

> I'm also not convinced that no-cache is the right way to signal invalidation. If the client receives a push promise with no-cache, I would interpret that to mean that all servers between the client and the origin (such as a CDN or reverse proxy between the client and the origin) have ensured that the pushed response is fresh from the origin server. This is what no-cache means: the response has been validated at the origin server. It does *not* mean that the client necessarily has an invalid version of the resource. What if the push promise has no-cache, and we take your idea to not cancel the push, but it turns out the client already has the most up-to-date version of the resource? We wasted time -- we could have canceled the push sooner. Better, IMO, to label the push promise with a conditional header describing the resource so the client can determine if the pushed response is more up-to-date than the resource it has cached. Also see this thread, which I forgot to link earlier:
https://lists.w3.org/Archives/Public/ietf-http-wg/2016JulSep/0522.html <https://lists.w3.org/Archives/Public/ietf-http-wg/2016JulSep/0522.html>

I assume you are referring to intermediaries (CDN/caching) sending push promises independently from the origin server?  From a technical point of view I completely agree with your assertion proxies MUST NOT send a push promise with a no-cache request directive unless they have validated the resource with the origin server.  However, this is the exact same model/flow as response no-cache directives, which is already implemented!  Namely, intermediaries should not replay push promises with a request no-cache directive without validating the cached response, just like it should not serve a cached response with a response no-cache directive without validating it.

> What if the push promise has no-cache, and we take your idea to not cancel the push, but it turns out the client already has the most up-to-date version of the resource? We wasted time -- we could have canceled the push sooner.  Better, IMO, to label the push promise with a conditional header describing the resource so the client can determine if the pushed response is more up-to-date than the resource it has cache.

This problem seems to be generic to the request/response model and cache directives, whether the request is initiated by the browser or by the server.  For example, if a client initiates a request with the no-cache cache directive, then it can be an equal waste of time if the cached response is already in fact valid.  I would hope that any solution to this problem would be generic and solve both cases.  Moreover, I believe existing work in progress (https://tools.ietf.org/html/draft-kazuho-h2-cache-digest-00 <https://tools.ietf.org/html/draft-kazuho-h2-cache-digest-00>) would solve this issue in a generic way AND be more efficient than canceling the push promise because with the proposal the push promise would not be sent in the first place!  

---
- David Witherspoon

---

From: Patrick McManus <[email-masked]>
Date: Fri, 26 May 2017 16:16:54 -0400

On Wed, May 24, 2017 at 1:27 PM, David Witherspoon <
[email-masked]> wrote:

> [

> validation on the origin server.

istm that you are confusing invalidation with use. 5.2.1.4 doesn't say
anything about invalidating the cache - bypassing when satisfying that
request is fine and even sensible if you expect multiple variants. Sec 4.4.
does talk about invalidation in the face of responses to unsafe methods but
a] 7540 does not allow pushing unsafe methods, and b] 4.4. is clear that
this only applies to caches that it "travels through" and its not clear
that 7540 push operates directly on a cache even though it has cache
semantics.

its not clear exactly how pushes should be applied by clients.. they are
making small movements towards "just replaying them" onto the cache
directly, but its obvious that can't be the whole story.. e.g. a response
containing cache-control: no-store is explicitly allowed for use by 8.2 and
that wouldn't work if promised streams were just directly use to populate a
cache.

> It would be semantically incorrect to cancel a server-initiated request
> with a no-cache request directive for the sole reason that it is already
> has a cached response.
>

I don't know that I would go as far as "semantically incorrect" because I
don't think you have the semantics you're looking for :).. but I do agree
that a cached-response that you wouldn't use to satisfy the pushed request
is not a good rationale for canceling the pushed stream. But as far as
correctness goes, you don't need a good reason to correctly cancel any push.

> In fact, the premise that a server-initiated request with a no-cache
> request directive can already have a cached response violates semantic
> equivalency. Any request, client or server initiated, with a no-cache
> request directive can not by RFC have a validated response in the cache.
>
>
I would agree with that. but I think you might also be implying that the
cache needs to invalidate any previously stored entry for the same URL..
and I don't agree with that :) (though it could.)

> Assuming the Push Promise goes through (client still may cancel the Push
> Promise for any reason), this clearly allows for overwriting an existing
> cached response, specifically replacing a cache entry.
>

I agree it allows that. it certainly doesn't MUST or even SHOULD it. It
might be reasonable.

---

From: David Witherspoon <[email-masked]>
Date: Fri, 26 May 2017 14:50:57 -0700

Yes, thanks for the clarification.  I agree invalidation is too strong of
wording.  Rather a request with cache-control: no-cache directive allows
for updating or adding entries to the cache.

> it certainly doesn't MUST or even SHOULD it.

I believe I might still disagree but let me clarify the scenario:

1. client issues request 1, and gets a cache-able response 1.
2. The client then issues the same request (request 2) but with the
no-cache cache request directive and gets a cache-able response 2 from the
origin server.
3.  The client issues request 3 that matches both previous requests and
assume both responses are valid.  rfc7234#section-4.4 states that "When
more than one suitable response is stored, a cache MUST use the most recent
response".  So response 2 would presumably be served from cache to answer
request 3  (OR It can also forward the request with "Cache-Control:
max-age=0" or "Cache-Control: no-cache" to disambiguate which response to
use.)

If we now assume that request 2 is server-initiated via a push promise, and
that the push promise is not cancelled, and the client "is just replaying"
it onto the cache then I would assume Step 3 MUST operate in the same way.

> I do agree that a cached-response that you wouldn't use to satisfy the
pushed request is not a good rationale for canceling the pushed stream

Agreed, this arose because Chrome has a PR that implements an optimization
on their implementation of just replaying it onto the cache.  That
optimization "Cancel[s] unnecessary push streams when it's discovered
they're in cache".  I believe the semantics of a no-cache request header is
well defined such that it can't already be discovered in cache.  Sure it
can cancel the Push Promise for any reason, but in this case, it is
ignoring the semantics that the no-cache request directive syntax defines.

Note, if response 2 has a longer validation lifetime then response 1, then
I believe it is behaviorally equivalent to invalidating response 1.

---

From: Amos Jeffries <[email-masked]>
Date: Sat, 27 May 2017 15:00:15 +1200

On 27/05/17 09:50, David Witherspoon wrote:
> Yes, thanks for the clarification.  I agree invalidation is too strong 
> of wording.  Rather a request with cache-control: no-cache directive 
> allows for updating or adding entries to the cache.
>
> no-cache cache request directive and gets a cache-able response 2 from 
> the origin server.
> 3.  The client issues request 3 that matches both previous requests 
> and assume both responses are valid.  rfc7234#section-4.4 states that 
> "When more than one suitable response is stored, a cache MUST use the 
> most recent response".  So response 2 would presumably be served from 
> cache to answer request 3  (OR It can also forward the request with 
> "Cache-Control: max-age=0" or "Cache-Control: no-cache" to 
> disambiguate which response to use.)
>
> If we now assume that request 2 is server-initiated via a push 
> promise, and that the push promise is not cancelled, and the client 
> "is just replaying" it onto the cache then I would assume Step 3 MUST 
> operate in the same way.

After that step-2 has occured there are only one of two situations possible:
* the cache ignored the response-2 and contains the response-1, or
* it replaced/updated the cache contents with response-2.

The Cache-Control header is exceptionally unlikely to be part of the 
Vary header list, so it is almost not possible to emit a single 
request-3 that causes the response-1 and response-2 to be treated as 
different representations of the same resource. It is possible they are 
different in some other way, but that no-cache itself is irrelevant to 
the variation identity.

>
> > I do agree that a cached-response that you wouldn't use to satisfy 
> the pushed request is not a good rationale for canceling the pushed stream
>
> Agreed, this arose because Chrome has a PR that implements an 
> optimization on their implementation of just replaying it onto the 
> cache.  That optimization "Cancel[s] unnecessary push streams when 
> it's discovered they're in cache".  I believe the semantics of a 
> no-cache request header is well defined such that it can't already be 
> discovered in cache.  Sure it can cancel the Push Promise for any 
> reason, but in this case, it is ignoring the semantics that the 
> no-cache request directive syntax defines.

The semantics of request CC:no-cache is that a cache *MAY* be updated. 
There is no requirement that the updates be stored for future clients.

PUSH simply proactively delivers a response that can be sent on as-is to 
the client, skipping delivery of whatever the cache contained.

If the cache decides to cancel a PUSH stream with no-cache or any 
similar revalidation requirement, the only thing it affects is to 
prevent itself from being able to PUSH that resource onwards. Middleware 
caches must wait to see if the client really does send that requirement 
and handle it in the normal way for such requests. Browser / UA caches I 
would expect to have better info on hand than a remote server - so that 
information should be informing the decision to cancel the push.

>
> Note, if response 2 has a longer validation lifetime then response 1, 
> then I believe it is behaviorally equivalent to invalidating response 1.

Only if the PUSH'd response is cached instead of cancelled. If it got 
cancelled there is no complete response object to do cache replacement with.

Amos