Subject: Re: Submitted new I-D: Cache Digests for HTTP/2

From: Kazuho Oku <[email-masked]>
Date: Tue, 2 Feb 2016 10:45:36 +0900

Thank you for your feedback.

2016-01-27 11:45 GMT+09:00 Eliezer Croitoru <[email-masked]>:
> I would like to join from this point to understand and ask the list since I
> couldn't follow and understand what was proposed and implemented exactly and
> I wanted to make sure I understand right.
>
> On 22/01/2016 19:47, Richard Bradbury wrote:
>>
>> Hello. The general thrust of this I-D seems like a useful optimisation
>> of HTTP/2 server push. It is wasteful to push a representation to a
>> client when the client already has a fresh copy cached. But the reverse
>> is equally true, I think...
>
>
> In some relation to the above quote I would like to ask:
> What is basically more important the client or the server resources?
>
> From what I understood the basic proposal was to add into every request the
> cache digest am I right? Is it still that way?

The original draft adds cache digest to every H2 connection.  Recent
discussion has been about conveying the digest within every HTTP
request as an HTTP header.

> Else then some privacy issues about sending the client cache-digest and TLS
> as being considered secure, there are other issues with it, for example
> mobile clients or metered WAN and LAN connections.
> If the client sends some KB(which can be more then couple cookies) on each
> request it means that for 20 requests the usage will be 10KB*20 <> 200KB
> which can become an issue for some but not all clients.

In case of HTTP/2, the overhead will be much less thanks to HPACK.
With HPACK, cache-digest that is sent repeatedly can typically be
compressed to one or two octets.  And unless HTTP/2 is being used,
there is practically no reason to send the cache digest over a public
network; only HTTP/2 supports push.

> Maybe for youtube that sends files\objects ranging from 3MB to 500MB++ it's
> not always an issue but sites that sends\pushes X*3MB images for the
> homepage to a mobile app is kind of an issue. If I'm not wrong this is one
> of the reasons that mod_pagespeed was designed, to somehow solve wrongly
> consumed bandwidth.
>
> From my point of view and understanding a cache-digest will probably require
> some per client "cache-digest dictionary" which can cause some issues to
> systems\servers with lots of clients\connections. The other side would be
> the ongoing re-validation and maintenance of this dictionaries.

That is a fair argument.  However, servers are already required to
maintain such dictionary for HTTP/2 (i.e. HPACK).

> It opens both the clients and the servers to some vulnerabilities. Also what
> would be the scope of the cache-digest, per connection? per request? per
> some client session id?
>
> And to polish out some aspects, what would happen if the server(which in
> many cases doesn't care about couple KB on the wire) will send a push offer
> for 20 objects and will be declined for each and every one of them with some
> kind of 304 by the client?
> - It will not require to open a new connection to the client and will use
> the same open connection.
> - It will not create a situation which the client resources(non-symmetric
> DSL clients) are being exhausted(imagine an office with 100+ PCs and 2 DSL
> 15MBit\1Mbit connection..)
> - It will simplify the server SW implementation and will prevent the need to
> store and look-up the client "cahce-digest dictionary" each and every time.
>
> And also if the html page contains the list of urls for objects that the
> client\browser can validate by itself someway, why do the client needs to be
> pushed some objects\content?(this is yet to be fully understood to me)
> I am looking for couple scenarios which will justify and clear out the need
> for such an implementation. Where is it needed else then advertisements?
> My basic understanding is that a cache-digest doesn't help for interactive
> applications or chats or real-time applications since the content there is
> always new or updated compared to the client. And compared to these a static
> files site will maybe require the client to send once the cache-digest but
> not on each and every request.
>
> I am almost convinced that:
> - Implementing a special request for an "update" request to a specific
> set\batch of files\objects will be much more efficient for both the client
> and the server then sending the cache-digest even once in a header.
> - Using some kind of push\offer 20 objects and being declined by the client
> would be much better then publishing the list of existing objects by the
> client.

Such approach is already defined as part of HTTP/2.

By using server-push defined in HTTP/2, it is possible for a server to
start sending resources that are expected to be used by the client.
However, the issue is that aggressively doing so wastes downstream
bandwidth, since without knowing what is already cached by the client
a server will repeatedly try to push the same objects (that are
rejected every time by the client after it receives the pushed
resource).

This draft is an attempt to fix the problem, by eliminating the
bandwidth you would waste if you push blindly, at the cost of some
upstream bandwidth.

> - For a client that doesn't care to send the header for 20 objects it would
> be pointless to not send if-modified-X requests for each and every one of
> these objects as an entity.
> - There are some security risks in the client sending a cache-digest in a
> specific scope which I would like to read about.
>
> Thanks,
> Eliezer
>
>

-- 
Kazuho Oku

---

From: Martin Thomson <[email-masked]>
Date: Tue, 2 Feb 2016 13:54:42 +1100

> maintain such dictionary for HTTP/2 (i.e. HPACK).

Note that a server would not be required to support cache-digest.  It
can ignore it entirely and push willy-nilly.  Or it can use the extra
information as it appears to optimize.  Also, contrary to what is
implied here, you don't need to maintain this as extra state if you
are happy to consume the header field on every request.

---

From: Eliezer Croitoru <[email-masked]>
Date: Tue, 02 Feb 2016 08:37:30 +0200

Thanks for the response!!
Your words made more sense to me now then before.

Eliezer

---

From: Richard Bradbury <[email-masked]>
Date: Tue, 2 Feb 2016 18:09:32 +0000

On 27/01/2016 04:38, Kazuho Oku wrote:
> 2016-01-23 2:47 GMT+09:00 Richard Bradbury:
>> ... This would allow a server to push a more
>> up-to-date version of a representation in the case where that representation
>> has been updated before the originally stated expiry. This allows a server
>> to supply the freshest possible version, overriding the client's (in this
>> case mistaken) belief that its cached copy is still fresh.
>>
>> You suggest below that a client would ignore such a push because it still
>> believes its copy to be fresh, thereby defeating the server's attempt to
>> push a fresher version.
> Actually I had thought the same.
>
> However, my current understanding is that Firefox behaves like that
> (i.e. ignore the pushed resources if a fresh entry already existed in
> cache), and from what I heard such behavior conforms to the HTTP/2
> specification.

I just re-read [RFC 7540 Section 8.2] and couldn't find any text 
explaining how a client should deal with pushed responses that are 
fresher than a cached item that is still believed to be fresh. The main 
requirement is that the promised request is cacheable, as defined by 
[RFC 7321 Section 4.2.3], and the specification then goes on to say that 
pushed responses can be cached by the client if it implements a cache. 
And that's about it as far as I can work out.

The implementation in Firefox you describe sounds like a reasonable, 
simple strategy for dealing with HTTP/2 server push that suits a limited 
set of common web browsing Use Cases. Yes, the implementation conforms 
to the HTTP/2 specification, but only because the specification is 
silent on what to do in the more advanced scenario I have introduced.

Pragmatically speaking, if a user agent is not willing to accept a 
pushed representation that is fresher than something it (incorrectly) 
believes to be still fresh, then your proposed "if-modified-since" 
conditional request is the next best way of getting fresher items into 
the client's cache. Because it relies on the client taking the 
initiative (to find out what is stale and what is still fresh) it does 
feel suboptimal, which rankles a bit. But, having bounced the idea 
around with you, it could be that a client-initiated mechanism is the 
best that can be achieved short of first clarifying the rules for 
caching pushed responses in the HTTP/2 specification, and then 
persuading browser developers to implement those rules.

>> The next question concerns syntax. Elsewhere in the thread I think it has
>> been suggested that there could be two types of digest transmitted from
>> client to server: one ("fresh") generated from URLs the client believes to
>> be fresh, the other ("if-modified-since") based on URLs that it believes to
>> be stale. Reading between the lines, am I right in thinking that the latter
>> is intended to be a sort of conditional client request, with a "304 Not
>> modified" response being pushed in response for those representations that
>> turn out not to be stale?
> Yes.
>
>> This all seems quite complicated, and I find the combination of parameter
>> name and semantics a bit confusing. For simplicity's sake I might be
>> inclined to just include the versioning information as standard in all cache
>> digests, in spite of the resulting overhead. Then the server can decide what
>> needs to be pushed in response after comparing the versioning metadata in
>> the received digest with the (potentially more up-to-date) information
>> available to the server. And, as an added bonus, you then don't need to
>> worry about defining what a pushed 304 response means :-)
> For stale entries, I believe that we should always push a response
> (which would either be a full response or a 304).  Otherwise, the
> client will issue a conditional request, and until the response for
> the conditional request becomes available, it may not be able to
> render the webpage (if the requested resource was blocking the
> critical rendering path).
>
> For fresh entries, we do not need to push a response.

Yes. That makes sense to me.

> Considering the fact that downstream bandwidth in the first few
> packets is precious due to slow-start, I think sending separate
> digests for fresh and stale resources is a better approach, since with
> the knowledge the server can avoid sending 304 for fresh resources.

Conserving downstream bandwidth is a key goal of this I-D, so I think 
that's a valid point in relation to your proposed "if-modified-since" 
conditional request, even if the actual saving in this particular case 
is quite small.

Thanks!

>> On Tue, 12 Jan 2016 10:04:00 +0900 Kazuho Oku wrote:
>>
>> 2016-01-11 2:11 GMT+09:00 Alcides Viamontes E:
>>> ...
>>> Here are the issues that I see:
>>>
>>> 1.- In its current wording, no information about which version of a
>>> representation the browser already has is present in the cache digest.
>>> That information can be included in the URL itself (cache busting),
>>> but then it becomes a concern for web-developers, adds complexity to
>>> their work, and bypasses the mechanisms that HTTP has in place for
>>> maintaining cache state.  It also increases space pressure in the the
>>> browser's cache as the server is left with no means to expire old
>>> cached contents in the browser.
>> That is a very good point.
>>
>> Let me first discuss the restrictions of the cache model used by HTTP,
>> and then go on to discuss what we should do if we are to fix the point
>> you raised.
>>
>> First about the restriction; the resources in the cache can be divided
>> into two groups: fresh and non-fresh.  A server should never push a
>> resource that is considered as fresh in the client's cache.  Clients
>> will not notice the push / the HTTP/2 allows client to discard such
>> push.  Therefore, a CACHE_DIGEST frame
>> must include a filter that marks the resources that are marked as
>> being fresh.  That is what the current draft specifies.
>>
>>
>> I think this is a reference to the sentence at the start of Section 2.1
>> stating: "The set of URLs that is used to compute Digest-Value MUST only
>> include URLs that share origins [RFC6454] with the stream that CACHE_DIGEST
>> is sent on, and they MUST be fresh [RFC7234]." In other words, according to
>> draft 00, the client-generated digest only includes the URLs of cached
>> representations it considers to be fresh.
>>
>> Next about the point of including version information (e.g.
>> Last-Modified, ETag) in the cache digest.  I believe we can add a
>> second Golomb-coded set to the frame that uses hash(URI + version
>> information) as the key.  A server can refer to the information to
>> determine whether if it should push a 304 response or a 200 response.
>>
>> The downside is that the CACHE_DIGEST frame may become larger (if the
>> server sends many responses that would become non-fresh), so it might
>> be sensible to allow the client to decide if it should send the second
>> Golomb-coded set.
>>
>> In addition, we should agree on how to push 304 response.  My
>> understanding is that HTTP/2 spec., is vague on this, and that there
>> has not yet been an agreement between the client developers on how it
>> should be done.
>>
>> Once that is solved, I think we should update the I-D to cover the
>> version information as well.

-- 
Richard.

---

From: Amos Jeffries <[email-masked]>
Date: Wed, 3 Feb 2016 09:26:22 +1300

> And that's about it as far as I can work out.

That is because PUSH by itself cannot do conditional requests. Consider
it to be the equivalent for a non-conditional request that always gets
the 200 status response with full new object and new expiry details.

The C-D information is needed from the client for the server to estimate
whether a conditional would have been used and a 30x status PUSHed.

> persuading browser developers to implement those rules.

Objects can be updated earlier than expected in normal HTTP proceedings.
That does not mean the client belief about freshness is wrong/incorrect.
The original object came with cacheability stating it was fresh for some
time period N. It just means there is a fresher copy that could also be
used for more accurate rendering if (and only if) the client happened to
request an update before that N period ran out.

Advertising, tag clouds, suggested linksets, etc. do this type of lazy
refresh routinely. Content could be a lot more dynamic than any one
client sees without raising bandwidth for the service as a whole.

Amos

---

From: Martin Thomson <[email-masked]>
Date: Wed, 3 Feb 2016 15:19:14 +1100

> the 200 status response with full new object and new expiry details.

This isn't strictly true.  You can server push a conditional request,
and in fact it can be advantageous to do so.

Of course, the server doesn't always know enough about a client to
make the *right* conditional request.

---

From: Mike Bishop <[email-masked]>
Date: Wed, 3 Feb 2016 19:01:48 +0000

We had talked at one point about intentionally pushing very specific conditional requests which would always have a 304 response.  The client, upon receipt, would either know that the resource in the cache was fresh (and could update cache lifetimes) or realize that an object they will need is not in the cache, and request it.

Not something that we've implemented yet, but it's a valid scenario.

-----Original Message-----
From: Martin Thomson [mailto:[email-masked]] 
Sent: Tuesday, February 2, 2016 8:19 PM
To: Amos Jeffries <[email-masked]>
Cc: HTTP Working Group <[email-masked]>
Subject: Re: Submitted new I-D: Cache Digests for HTTP/2

On 3 February 2016 at 07:26, Amos Jeffries <[email-masked]> wrote:
> That is because PUSH by itself cannot do conditional requests. 
> Consider it to be the equivalent for a non-conditional request that 
> always gets the 200 status response with full new object and new expiry details.

This isn't strictly true.  You can server push a conditional request, and in fact it can be advantageous to do so.

Of course, the server doesn't always know enough about a client to make the *right* conditional request.

---

From: Alcides Viamontes E <[email-masked]>
Date: Tue, 9 Feb 2016 12:46:36 +0100

>> Not something that we've implemented yet, but it's a valid scenario.

Pushing 304 works both in Chrome and Firefox:
https://drive.google.com/open?id=0B2F2m0rSqGCVWFJnTzRWOWFWQmc , we have
been doing it for some time.  But I agree that the HTTP/2 spec is very
vague regarding PUSH and cache interaction, I wish that could be
improved....

On Wed, Feb 3, 2016 at 8:01 PM, Mike Bishop <[email-masked]>
wrote:

> We had talked at one point about intentionally pushing very specific
> conditional requests which would always have a 304 response.  The client,
> upon receipt, would either know that the resource in the cache was fresh
> (and could update cache lifetimes) or realize that an object they will need
> is not in the cache, and request it.
>

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 10 Feb 2016 10:50:34 +0900

Hi,

2016-02-09 20:46 GMT+09:00 Alcides Viamontes E <[email-masked]>:
> https://drive.google.com/open?id=0B2F2m0rSqGCVWFJnTzRWOWFWQmc , we have been
> doing it for some time.

My understanding is that handling of pushed 304 in Chrome and Firefox
is unreliable.

When sending a push, a server cannot be 100% certain if the client has
the resource cached.  In other words, there is always a possibility
that the pushed response will be considered as a response to a
non-conditional HTTP request on the client side.

In other words, browsers that support 304 push should, when matching a
pushed 304 response against a HTTP request, check that the request is
conditional, and use the pushed response only if the request was
conditional (additional checks might be necessary).  Otherwise, the
pushed 304 request must be ignored, and the browser should pull the
unconditional HTTP request.

However, my understanding is that both Chrome (48.0.2564.103) and
Firefox (44.0.1) don't do the check; they consider pushed 304
responses to be a response to a unconditional HTTP request.
Therefore, there is a chance that you would fail to deliver the
correct content if you use 304 push today.

-- 
Kazuho Oku

---

From: Stefan Eissing <[email-masked]>
Date: Wed, 10 Feb 2016 08:02:10 +0100

Is PUSHing a HEAD request, unconditional, not what you are looking for?

> Am 10.02.2016 um 02:50 schrieb Kazuho Oku <[email-masked]>:
> 
> Hi,
>

---

From: Alcides Viamontes E <[email-masked]>
Date: Wed, 10 Feb 2016 08:18:48 +0100

Thanks Kazuho, I was not aware of that. It looks like I have some fixing to
do!

Bests,

./Alcides.

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 10 Feb 2016 16:26:51 +0900

> Is PUSHing a HEAD request, unconditional, not what you are looking for?

Thank you for the suggestion.  I hadn't thought about using HEAD, but
it sounds like an elegant solution.

Pushing HEAD requests with validators stored in the responses would be
much easier and straightforward to define and / or implement than
trying to determine how to push conditional requests.

Do the web browsers recognize pushed HEAD requests?

-- 
Kazuho Oku