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

From: Alcides Viamontes E <[email-masked]>
Date: Sun, 10 Jan 2016 18:11:44 +0100

Hello,

My interest in the draft "Cache Digests for HTTP/2"
https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
concerns the original, intended use case that Mr. Kakuho Oku and Mr.
M. Nottingham cited. As the authors, I would like very much like to
see this made a standard and implemented in browsers. However, I
perceive a few issues. Beforehand, I apologize for this long email,
for any gaps in my understanding of the subject, and for not being
familiar with the language and procedures used in this list.

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.

2.- There is no way for the server to know that a CACHE_DIGEST frame
is coming immediately after a HEADERS frame. A server may trigger some
processing already after the end of headers has been received, while
making  further DATA frames available as a stream of data to the
application. With CACHE_DIGEST frames, the cache aware server will
have to delay processing until the end of the stream has been seen to
be sure that no CACHE_DIGEST frame is coming, or would have to
re-start processing on seeing the frame. Arguably this is not a big
problem for GET requests with an empty body, but it would be nice if
the spec didn't force the server to wait for the end of the stream.

3. - Traditionally, cache state information has been placed in HTTP
header fields. A CACHE_DIGEST frame puts some of that information in a
new place, which is sure to cause some pain to web developers and
sys-admins trying to understand the behavior of their applications.

4.- The draft assumes a somewhat more restricted scope of Push than
allowed by the HTTP/2 spec, RFC7540, and to some extent, goes against
current practice. Section 8.2 of RFC7540, "Server Push", says "The
server MUST include a value in the :authority" pseudo-header field for
which the server is authoritative". Section 10.1 defines server
authority by referring to [RFC7230], Section 9.1. For the HTTPS case,
a server is authoritative for a domain if it can present a certificate
that covers that domain. To the point, RFC7540 does not forbids a
server to push resources for different domains, provided that it has
the right credentials. Pushing assets for a domain different than the
one where the request is received is useful when considering the way
web applications are structured today: many serve their application
logic using a www.example.com domain, while serving their static
assets at static.example.com . Therefore, upon receiving a request to
www.example.com, a server may want to push resources for
static.example.com. However, section 2.1 of the draft works against
that use case.

5.- A last issue has to do with what to include in the cache digest.
Mr.  Oku proposes to only push resources which are in the critical
render path in his article at [1]. Correspondingly, the cache digest
would only need to include those resources. Can we have a simple
mechanism to control the cache digest contents?

I can provide some data and some rough suggestions to address the issues above.

How big would a cache digest be anyway?
-------------------------------------------------------

To address issues 2 and 3 we need to determine how constrained we are
regarding space. We have made a little study[2] across 1300 sites
submitted by performance-conscious site operators, and from there we
can establish that while 50% of sites fetch between 25 and 110
resources, it is not too rare to have sites doing more than 200 HTTP
requests. If anything, that number is going to grow. Specially with
HTTP/2. Let's then use 200 as a ballpark estimate of the number of
items in a cache digest and start from there.

The source that the draft includes for Golomb-coded-sets (GCS) hints
that it is possible to use the number of bits in a Bloom filter as an
upper bound for the size of the corresponding GCS. Therefore, with a
digest of size 200, we would be using an upper bound of 200*1.44*512
bits, which is around 18 kB is expressed as binary, and around 24 kB
if expressed in ascii form, base64-encoded, assuming a false positive
probability of 1/512.

Notice that by using PUSH the browser may skip many of those requests.
In our site (https://www.shimmercat.com), we have measured HTTP/2
requests averaging at 60 bytes per request. Therefore, one may end up
saving up to 200 * 60 = 12  kB in traffic, bringing down the previous
numbers to 18 kB -12 kB =6 kB and 24 kB - 12 kB = 12 kB. I think that
12 kB is acceptable for a site with 200 requests, specially since
HTTP/2 PUSH would greatly increase the data transfer density for those
sites.

Can we embed the cache digest in a header?
------------------------------------------------------------

Having 24 kB of cache digest in a header may delay processing the
request more than acceptable, since most servers will wait to get the
entire header block before starting to create an answer. There is an
alternative however, and that would be to put a  field with the cache
digest in a request trailer, allowed with chunked transfer under
HTTP/1.1 and in all streams with HTTP/2. The pros of having the cache
digest in a header or trailer field are the following:  we don't break
with the tradition of exchanging cache state through headers,  headers
are visible to developers' tools, it would be possible to test things
using polyfills and service workers while the browsers catch up with
native implementations, no extensions to HTTP/2 are needed, and cache
digests would become possible even over plain old HTTP/1.1. It can
also be made a little more future-proof:

In the headers:

       cache-digest: trailers

(the indication above is not needed however if the cache-digest-scope
is used, see below)

In the trailers:

         cache-digest: data:application/golomb-coded-set;base64,.....

The cons is that ascii is bigger than binary.

Even if the CACHE_DIGEST frame is pursued, it would be nice to have

         cache-digest: frame

as part of the request (and this time in the headers section, not the
trailers) for the server to recognize that a cache digest frame is
coming and for developers to have a hint that said information is
being transmitted between client and server.

Distinguishing representation versions in the cache digest (Addressing point 1)

---------------------------------------------------------------------------------------------------------

The GCS filter requires the client and the server to be able to
compute the same hash key for a given resource and version. As far as
I understand, having semantics here similar to if-modified-since would
not be possible. But strong etags could be used when computing the
key, therefore enabling the equivalent to if-none-match. Step 4 in the
algorithm of section 2.1 of the draft could be extended to have the
etag used together with the URL when taking the hash.

Which representations should be part of the digest? (Addressing point 4 and 5)
--------------------------------------------------------------------------------------------------------

I suggest to introduce the concept of cache digest scope. Only
representations which were given a cache digest scope would be made
part of a cache digest. And the set of representations URLs to be
included by the client in the digest would be the intersection of:

1. The set of representations that have the same cache digest scope in
the browser's cache than the domain of the first request (the
document), and
2. The set of representations in the browser's cache for which the
server is considered authoritative.

The cache digest scope would be unique per domain.

In other words, it would look like the following:

Client asks for
                  https://www.example.com/
Server answers, and adds a header
  cache-digest-scope: example
The server then answers or pushes
   https://static.example.com/styles.css ,
        it uses the same header
           cache-digest-scope: example.

The server also answers or pushes
   https://media.example.com/hero-1.png,
       but no cache-digest-scope is provided.

.... some time after, when a new connection is established by the same
client to fetch another page from the same domain:

Client asks for (a different page)
         https://www.example.com/page1.html ,
        now the client specifies a header
       cache-digest-scope: example
        client also provides a cache digest with all
        the resources that were assigned
        the same cache digest scope by the server.
        That digest would include the resource from
https://static.example.com/styles.css
        but not the one at
                 https://media.example.com/hero-1.png

The server answers and pushes a 304 not modified for
https://static.example.com/styles.css ,
        or a 200 with new contents, using a cache contents
        aware PUSH_PROMISE frame.

This  mechanism addresses 4 by allowing digests to extend over
multiple domains, and addresses 5 by allowing the server to control
which assets are part of the digest: resources *without* the
"cache-digest-scope" header are never made part of the digest. Also,
the holder of a wildcard certificate can still use it to host separate
multi-domain applications, for example (app1.example.com,
static1.example.com with cache digest scope "1") and
(app2.example.com, static2.example.com with cache digest scope "2"),
without fearing the cache digest to grow too big. Furthermore, if a
server doesn't implement PUSH or otherwise doesn't use the cache
digest, it implicitly opts out of cache digests,  saving bandwidth.

The cache-digest-scope: xxxx header would be idem in most requests and
responses, and HPACK in HTTP/2 could compress it to a few bytes by
using the dynamic table.

Best regards,

----
Alcides.

[1] http://blog.kazuhooku.com/2015/12/optimizing-performance-of-multi-tiered.html
[2] http://nbviewer.ipython.org/github/shimmercat/art_timings/blob/master/TimingsOfResourceLoads.ipynb

---

From: Alex Rousskov <[email-masked]>
Date: Sun, 10 Jan 2016 12:36:40 -0700

On 01/10/2016 10:11 AM, Alcides Viamontes E wrote:

> Let's then use 200 as a ballpark estimate of the number of
...
> Therefore, with a
> digest of size 200, we would be using an upper bound of around 18 kB

Hm.. If 200-URL site is the primary target, then a 1:1 bitmap (200 bits
or 25 bytes) can be used. The origin server can return an HTTP header
indicating the bitmap index of the response so that the client knows
which digest bit to turn on for that cached response.

With just 200 URLs, maintaining a set of unique URL indexes cannot be
very difficult and, with an addition of a "digest version" (4 more
bytes), can be fully automated to accommodate changes in the URL set.

Alex.

---

From: Cory Benfield <[email-masked]>
Date: Mon, 11 Jan 2016 08:16:07 +0000

> On 10 Jan 2016, at 17:11, Alcides Viamontes E <[email-masked]> wrote:
> 
> ——————————————————————————————
> 

On a personal level I am extremely nervous about shoving 24kB of data into a header value. The practice of doing this for Kerberos tokens already caused us to require the CONTINUATION frame unpleasantness in RFC 7540. Generally speaking it seems like smuggling long strings in HTTP headers is a bit of an anti-pattern, and given that HTTP/2 gives us much nicer methods of transporting this kind of data it seems a shame not to use them.

Cory

---

From: Chris Bentzel <[email-masked]>
Date: Mon, 11 Jan 2016 05:42:20 -0500

The draft does cover some privacy concerns (such as clearing the digest
when cookies are cleared).

One concern not covered is how to deal with cases where a client may have
cached content from an origin with a mix of cookies. For example, if a user
has enabled third-party cookie blocking in a browser and has visited an
origin in both a first-party and third-party context there may be a mix of
cached content with a session identifier cookie and no cookie.

If the user re-visits that origin in a first-party context, the digest may
reveal content retrieved in a third-party context.

One option is to treat cached content as if there is an implicit Vary:
Cookie header and only include in the digest if it matches. The draft
already requires only including fresh cached entries in the digest so a
selection process for cached entries already will need to exist.

On Mon, Jan 11, 2016 at 3:16 AM, Cory Benfield <[email-masked]> wrote:

> On a personal level I am extremely nervous about shoving 24kB of data into
> a header value. The practice of doing this for Kerberos tokens already
> caused us to require the CONTINUATION frame unpleasantness in RFC 7540.
> Generally speaking it seems like smuggling long strings in HTTP headers is
> a bit of an anti-pattern, and given that HTTP/2 gives us much nicer methods
> of transporting this kind of data it seems a shame not to use them.
>
> Cory
>
>

---

From: Ilya Grigorik <[email-masked]>
Date: Mon, 11 Jan 2016 07:39:34 -0800

Glad to see this proposal!

FWIW, another +1 for enabling this functionality via an HTTP header.
Limiting it to h2 frames makes it effectively inaccessible to web
developers that want to experiment with own cache management logic (via
ServiceWorker, etc).

ig

On Mon, Jan 11, 2016 at 2:42 AM, Chris Bentzel <[email-masked]> wrote:

>> On a personal level I am extremely nervous about shoving 24kB of data
>> into a header value. The practice of doing this for Kerberos tokens already

---

From: Kazuho Oku <[email-masked]>
Date: Tue, 12 Jan 2016 10:04:00 +0900

> familiar with the language and procedures used in this list.

Thank you for your feedback!

> 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.

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.

> the spec didn't force the server to wait for the end of the stream.

Agreed.

There are three options here (the draft adopts option C):

a) send CACHE_DIGEST frame right before HEADERS
b) send CACHE_DIGEST frame at stream_id=zero, with the value of the
authority that should be associated to the digest included within the
frame
c) send CACHE_DIGEST frame right after HEADERS

B is clearly the easiest but would have a small impact on the consumed
bandwidth, since the authority needs to be sent separately.

In A, the server does not need to delay the processing of the request,
but needs to cache the value of the digest.

It would be great to discuss which of the three approach will be the
best solution in general (or if there could be other approaches).

> sys-admins trying to understand the behavior of their applications.

CACHE_DIGEST frame should not be a HTTP header, since including the
value in every HTTP request (as a header) will make the HTTP requests
huge.  Since the client's cache state changes as the server sends
responses, we cannot expect HPACK to effectively compress the
requests.  We should send cache digest only once per HTTP/2
connection.
(note that intermediaries are allowed to re-order the HTTP requests
sent from a client, so it is impossible to include the digest only in
the first HTTP request as a header).

The other reason is that the digest should be hop-by-hop.  The default
behavior of a proxy (that do not understand the extension) should be
to drop the digest.

> static.example.com. However, section 2.1 of the draft works against

Thank you for pointing that out.

I think that for plaintext HTTP we agree that the client needs to
associate the name of the authority to the digest that it sends
(including one of the three options discussed above).

Considering the case for HTTPS, may be we should better allow the
client whether or not to associate an authority.

> mechanism to control the cache digest contents?

It is obvious that providing a way to specify the resources that
should be included in the cache digest will let clients generate more
compact digest values.

The downside is that it would be difficult for server administrators
to _change_ a resource to become part of the digest.  Consider the
case where a server has send resource A that is not being marked as
part of the digest, and then the server administrator then changes the
configuration for resource A to be included in part of the digest.
The client will not include A in the digest it sends, since it is not
marked.  The server will push the A to the client since it is not
included in the digest.  (As discussed above) a client may discard the
resource being pushed.  So A will continued to be pushed every time a
new request is issued.

Considering such possibility, it would be less troublesome if we could
go without introducing a way to configure what should be included in
the digest.

> How big would a cache digest be anyway?

Your calculation is wrong.  A 200-entry GCS (with 1/512 false positive
rate) will be slightly larger than 225 bytes (log2(512) * 200 bits) in
binary form.

-- 
Kazuho Oku

---

From: Kazuho Oku <[email-masked]>
Date: Tue, 12 Jan 2016 10:20:11 +0900

2016-01-12 0:39 GMT+09:00 Ilya Grigorik <[email-masked]>:
> Limiting it to h2 frames makes it effectively inaccessible to web developers
> that want to experiment with own cache management logic (via ServiceWorker,
> etc).

Glad to hear from you.

While it is possible to use an HTTP header to implement cache-digest
(and that is what we are doing now in H2O + ServiceWorker/cookie), I
believe it should ideally be implemented as an HTTP/2 header since:

* including the digest value (the value changes as client receives
responses) in every HTTP request is a waste of bandwidth
* cache state is an information that is bound to the connection, not
to a request

There are hacks that be used to evade such problems:

* use multiple headers (send a big constant + small delta that changes
frequently) to represent a single digest for HPACK efficiency
* do not send cache digest if it is likely than an intermediary exists

but I do not think we should include such things in the spec.

>>> Generally speaking it seems like smuggling long strings in HTTP headers is a
>>> bit of an anti-pattern, and given that HTTP/2 gives us much nicer methods of
>>> transporting this kind of data it seems a shame not to use them.
>>>
>>> Cory
>>>
>>
>

-- 
Kazuho Oku

---

From: Alcides Viamontes E <[email-masked]>
Date: Tue, 12 Jan 2016 10:01:55 +0100

Hello,

Thanks for your response Kazuho. I need to do some thinking before
fully addressing your comments, since you add valuable information
that I didn't consider before. At the risk of adding some noise

> rate) will be slightly larger than 225 bytes (log2(512) * 200 bits) in

That's good news! My Google's cookie is 1246 bytes long. If we are not
talking about several kilobytes, then the restrictions are less.

I would like to know more about the expectations for intermediaries.
As of today, HTTP/2 is working with TLS, therefore the website
operator needs to bless the HTTP/2 edge server with the SSL
certificate's private key. Maybe we can hope that website operators
will choose an HTTP/2 stack that does what he/she intends? In other
words, I think we shouldn't make the spec more difficult to use just
to accommodate potential issues with intermediaries. In that light, we
could perhaps require intermediaries to use a cache digest mindfully.
In any case please forgive me for my lack of data, this is just food
for thoughts and I will be glad to know more about how things look
right now with HTTP/2 intermediaries and caches.

I will write a more detailed follow-up a few weeks from now. I will
also try to make a polyfill implementation using service workers and
ShimmerCat to learn how this looks in practice.

Bests,

./Alcides.

---

From: Stefan Eissing <[email-masked]>
Date: Tue, 12 Jan 2016 10:46:23 +0100

Kazuho,

thanks for the draft and your work in this. I am currently implementing
a "push diary" for connections in mod_http2. This updates itself while
handling the connection, but ideally is initialized by a CACHE_DIGEST
frame.

> Am 12.01.2016 um 02:04 schrieb Kazuho Oku <[email-masked]>:
> [...]

There seems to be an underlying assumption here that CACHE_DIGEST is 
connected to a specific authority which was not immediately obvious 
to me when reading the draft.

With that in mind, I would prefer the CACHE_DIGEST frame to
carry the authority scope. That makes it self descriptive and
it can be sent on stream 0, which solves the before/after problem.

Also, I think the authority in that frame should be allowed to carry
the value '*' to allow endpoint implementations that keep one set
for all authorities on a connection.

Additionally:

1. Just for clarification: the digest values are calculated on 
the absolute URL, :scheme+'://'+:authority+:path? Because h2o
currently uses only :path, if I read it right.

2. Would it make sense to limit N and P, so that (N*P) which is
used for modulo calculation stays in unsigned 32 bit? Even with
N==P it would work up to 65K, if I am not mistaken...

-Stefan

---

From: Stefan Eissing <[email-masked]>
Date: Tue, 12 Jan 2016 10:50:39 +0100

> Am 12.01.2016 um 10:46 schrieb Stefan Eissing <[email-masked]>:

64K, my bad.

---

From: Stefan Eissing <[email-masked]>
Date: Tue, 12 Jan 2016 17:31:38 +0100

Kazuho,

while pondering my implementation: ch. 2.1 step 8 is different from the algorithm described in http://giovanni.bajo.it/post/47119962313/golomb-coded-sets-smaller-than-bloom-filters

Your draft calculates (as I read it, I could be wrong)
A.  for i in 0..len-2; do D:="hash-values[i+1] - hash-values[i] - 1"; ....; done

while the latter does
B.  for i in 0..len-1; do D:="hash-values[i] - (i > 0)? hash-values[i-1] : 0)"; ....; done

I am not sure, on decompression, how to obtain the first hash value back from A. In B it just is the first delta. Could you give me a hint?

Thanks!

-Stefan

> Am 12.01.2016 um 10:01 schrieb Alcides Viamontes E <[email-masked]>:
> 
> Hello,
>

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 13 Jan 2016 06:33:32 +0900

> I am not sure, on decompression, how to obtain the first hash value back from A. In B it just is the first delta. Could you give me a hint?

The algorithm intended here is as follows.  Please let me check the spec.

uint64_t next_min = 0;
for (i = 0; i < hash_values.length; ++i) {
  D = hash_values[i] - next_min;
  ...
  next_min = hash_values[i] + 1;
}

The corresponding code in my implementation is
https://github.com/kazuho/golombset/blob/e7cae04/golombset.h#L154-L158

-- 
Kazuho Oku

---

From: Amos Jeffries <[email-masked]>
Date: Wed, 13 Jan 2016 12:17:20 +1300

On 12/01/2016 2:20 p.m., Kazuho Oku wrote:
> 2016-01-12 0:39 GMT+09:00 Ilya Grigorik:

Bandwidth may or may not be a problem relative to the digest design and
amount of compression applied by the protocol (eg. h2 dynamic table vs
HTTP/1.1 repetition).

> * cache state is an information that is bound to the connection, not

You assume a browser endpoint cache.

Intermediary caches are constructed from content flowing over multiple
parallel connections. Potentially from multiple origins. Which makes it
very likely to have changed between any two given requests to contain
things that cannot be inferred by the server from those two requests.

This type of problem is also more likely to happen in the presence of
domain sharding. Where the temporal locality of the index request is
different from the 100's of content requests.

ALTSVC may also make similar things happen with browser caches.

Amos

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 13 Jan 2016 10:47:39 +0900

> handling the connection, but ideally is initialized by a CACHE_DIGEST

My pleasure.  It's great to hear that you are also looking into optimizing push.

> for all authorities on a connection.

Thank you for expressing your preference.

Number of additional octets needed for embedding the authority name
will typically far less than INITCWND (in many cases around 15KB);
hopefully we can all agree to accept the overhead.

> currently uses only :path, if I read it right.

Correct.

> N==P it would work up to 65K, if I am not mistaken...

That is possible, but I am not sure if we should optimize that much.

The computational cost of restricting N*P to 32bit (as opposed to
64bit proposed by Martin) should be negligible; the dominant factor
for encoding / using the digest will be calculating the SHA256 of the
URLs.

Regarding the cost of space, it is possible to truncate the values
obtained from GCS after decoding them.

-- 
Kazuho Oku

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 13 Jan 2016 11:30:54 +0900

2016-01-11 19:42 GMT+09:00 Chris Bentzel <[email-masked]>:
> The draft does cover some privacy concerns (such as clearing the digest when
> cookies are cleared).
>

Thank you for raising the concern.

My understanding is that it is already possible to track users that
reject third-party cookies using cache-based fingerprint.  The change
in this draft that such tracking becomes passive.

> selection process for cached entries already will need to exist.

Interesting.

IMO, web browsers configured to reject third-party cookies should
ideally set implicit `Vary: host` for every entry it caches.  Doing so
not only solves passive fingerprinting (which becomes possible with
this draft), but also will prevent active fingerprinting as well
(which is already possible).

If such change cannot be made within the web browsers, then we should
look for a workaround...

-- 
Kazuho Oku

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 13 Jan 2016 14:04:07 +0900

> amount of compression applied by the protocol (eg. h2 dynamic table vs

That is generally true.

However the specification will become complicated if we are to include
the cache digest in the headers, while achieving a good compression
ratio.

To send cache digests using headers while achieving good compression
ratio with HPACK, we would need to split the digest into at least two
headers: a large value that changes infrequently, and small values
that change frequently.  A client will send those headers attached to
every HTTP request, a server will merge the header values into one and
decode the digest for every HTTP request.

And even if we did include such headers in every HTTP request, it is
still better for a  server to maintain the client's cache state per
connection, since there is a time slot between when a server sends a
resource (so that it would get cached by the client), and when a
client is capable of notifying the server that it has actually cached
something.  For example, if a client requests `/a.html` and `/b.html`
(that both rely on `/style.css`), a server should push `/style.css`
only once.  To do so, the draft expects a server to maintain and
update the estimated cache digest of the client, connected over a
single TCP connection.

To summarize, the draft utilizes the fact that HTTP/2 multiplexes HTTP
requests into a single, ordered stream to make things simple.
Considering the fact that we need to rely on HTTP/2 to push things
anyways (that is the primary target of the draft), I think that is a
reasonable trade-off.

> things that cannot be inferred by the server from those two requests.

Do you consider that caching proxies should establish multiple
connections to upstream when using HTTP/2?

-- 
Kazuho Oku

---

From: Stefan Eissing <[email-masked]>
Date: Wed, 13 Jan 2016 10:08:12 +0100

> Am 13.01.2016 um 02:47 schrieb Kazuho Oku <[email-masked]>:
> 

I missed the proposal by Martin. A 64 bit limit will also work for me.

-Stefan

---

From: Stefan Eissing <[email-masked]>
Date: Wed, 13 Jan 2016 10:14:59 +0100

> Am 12.01.2016 um 22:33 schrieb Kazuho Oku <[email-masked]>:
> 

Thanks. That is how I understood the original article (apart from the +1, which is clever). 

I'll continue my implementation and, based on that, may make a proposal for an alternative description of the algorithm, if that is fine with you.

Cheers,

  Stefan

---

From: Stefan Eissing <[email-masked]>
Date: Wed, 13 Jan 2016 10:40:31 +0100

> Am 13.01.2016 um 06:04 schrieb Kazuho Oku <[email-masked]>:
> 

There might be use cases to 
a) transport a cache digest over HTTP/1.1
b) expose a cache digest to a web application

I think the draft could define a header field for this purpose
and describe its use. Specifically
- HTTP/1.1 clients should make it a Connection header
- HTTP/1.1 to H2 transformers may use it in calculating their 
  CACHE_DIGEST frames (depending on their caching strategy)
- similar for H2 to HTTP/1.1 gateways

So this header, let's call it "Cache-Digest" for the sake of 
discussion, could appear in HTTP/1.1 requests or on web server
and clients internal APIs:

...
Cache-Digest: <base64url encoded, golombset compressed digests>
Connection: Cache-Digest
...

The question is what a H2 origin server does with such a header, 
should it appear. Ignore, discard?

I don't see that the draft should care about H2 header 
compression efficiency of such a beast. Sending it over H2
seems more a curiosity to me.

-Stefan

---

From: Amos Jeffries <[email-masked]>
Date: Wed, 13 Jan 2016 22:40:44 +1300

> connections to upstream when using HTTP/2?

Typical ISP intermedaries service on the order of 20K requests per
second. Most of that traffic goes to just a handful of origins these
days. So yes multiple connections to at least those origins are going to
happen.

Amos

---

From: Ilya Grigorik <[email-masked]>
Date: Wed, 13 Jan 2016 13:03:30 -0800

> seems more a curiosity to me.

Agreed. As was highlighted previously, exposing it via a header makes it
accessible to web developers (and removes the requirement for UA to support
this "natively" to get any benefit or use), which (to me, at least) far
outweighs the benefits of saving a few bytes.

ig

---

From: Kazuho Oku <[email-masked]>
Date: Thu, 14 Jan 2016 17:55:15 +0900

2016-01-14 6:03 GMT+09:00 Ilya Grigorik <[email-masked]>:
>
> On Wed, Jan 13, 2016 at 1:40 AM, Stefan Eissing
> <[email-masked]> wrote:
>>

I like Stefan's proposal to include a definition of how Cache Digest
should be encoded as a header.

Defining it will increase the flexibility of how the value can be used.

> outweighs the benefits of saving a few bytes.

I understand your points.

However, the issue is that the information is hop-by-hop.  In terms of
HTTP/1, a header specifying the cache digest should be included in the
`Connection` header.  However, both XMLHttpRequest and Fetch API (IIRC
on which ServiceWorker depends) disallows applications to modify the
Connection header.

So a SW trying to send cache digest needs to send the value without
designating it as a hop-by-hop header.  And that means that if a H2
origin server sees the header, it needs to determine if the request
came through an intermediary, and only use the value if it did not.
Or else, a server might end up repeatedly pushing resources to a
caching proxy that already has them cached.

This looks like a hack to me, and I am not sure if it should be
permitted within a specification, even though I believe it would work
effectively (as we are doing in H2O).

-- 
Kazuho Oku

---

From: Kazuho Oku <[email-masked]>
Date: Thu, 14 Jan 2016 18:14:03 +0900

> days. So yes multiple connections to at least those origins are going to

Thank you for the clarification!

That means that regardless of whether we use an H2 frame or an HTTP
header to represent the cache digest, we would have a trade-off
between: a) the cost of spending upstream bandwidth to update cache
digest vs. b) the cost of spending downstream bandwidth due to servers
pushing the same resource though different connections.

Would that be a practical issue for caching proxies implementing cache
digests?  If it is, it might be better for us to define how to make
delta updates to the cache digest maintained on the server side.  That
would help caching proxies update the digest more frequently
(ultimately every time the cache state changes), leading to less
unnecessary pushes.

-- 
Kazuho Oku

---

From: Cory Benfield <[email-masked]>
Date: Thu, 14 Jan 2016 11:12:53 +0000

> On 13 Jan 2016, at 09:40, Stefan Eissing <[email-masked]> wrote:
> 
> ...

According to RFC 7540 a H2 origin server would have to reject that stream with RST_STREAM(PROTOCOL_ERROR): see Section 8.1.2.2 for the normative MUST NOT.

For that reason, I think this is a reasonable extension but *only* if the header is defined as connection-specific. Assuming that Kazuho’s objections relating to XMLHttpRequest and FetchAPI are resolved, this approach seems reasonable.

One approach to resolving those problems would be to add a requirement that H2 endpoints treat a stream with the Cache-Digest header in it as a stream error with some useful error flag. That requirement, well policed by origin servers, would probably resolve the issue neatly.

Cory

---

From: Ilya Grigorik <[email-masked]>
Date: Thu, 14 Jan 2016 06:13:01 -0800

> effectively (as we are doing in H2O).

With HTTPS in place, I think you can make some reasonable simplifying
assumptions here.. that'll make things work in practice.

ig

---

From: Kazuho Oku <[email-masked]>
Date: Fri, 15 Jan 2016 03:49:44 +0000

Agreed.

I still think this is very tricky, but we could possibly define
Cache-Digest header as a hop-by-hop header that would be capable of
representing the contents and flags of a CACHE_DIGEST frame, and state
that:

* the header SHOULD NOT be sent over an HTTP/2 connection
* a gateway MAY use the header to transfer the cache-digest value of a
client to upstream
* a client incapable of creating HTTP/2 frames (e.g. XMLHttpRequest,
Fetch API) MAY use the header in place of the CACHE_DIGEST frame; if
and only if it is estimated that the underlying connection will
directly connect to the origin server (ie. HTTPS)
* a server MAY handle the value of the header as if it was an HTTP/2 frame

Besides, we should alter how a server should behave when seeing the
digest more than once, considering the fact that HTTP/1 APIs do not
generally have the guarantee that the requests will be sent in the
order the API was called.  When receiving a new cache digest, a server
should merge the value with the value already in possession, instead
of replacing the old value with the new one.

This change will actually help the case in which multiple H2
connections are established between the client and the server; since
by changing the meaning of following CACHE_DIGESTs from replace to
merge, it becomes possible to update the cache digest maintained on
the server side by only sending the delta of the changes that happened
on the client side.

We might still want to define how a digest value can be unset from the
server-maintained digest; one way will be to use a flag to indicate
whether if the CACHE_DIGEST frame is a delta to the previous or if is
a replace.  Or clients can simply close the H2 connection to clear the
server-side digest.

Also, we may need to consider how CACHE_DIGEST-aware web browsers
should communicate with SW.

Considering the fact that SW is essentially a proxy implemented within
the browser, I believe CACHE_DIGEST-aware web browsers should include
within the request it passes to SW the digest of the browsers' cache
using `Cache-Digest` header.

SW may alter the value, and then issue an HTTP request with the
`Cache-Digest` header using Fetch API.

The Fetch API implementation of the web browser should convert the
value supplied in the `Cache-Digest` header to a HTTP/2 frame before
sending the request over the network.  It should also try to minimize
the size of the frame, by calculating the difference between the
supplied digest value and the previously-sent values, and only send
the difference as the delta (as discussed in the above issue).

Or else, there should be an interface for reading / writing cache-digest values.

It should also be noted that IIRC SW cannot be defined for an entire
domain.  That means that in case SW is registered (and if SW is
capable of altering the cache-digest), a client MUST NOT try to send
cache digest for the entire domain even when other conditions permit
(i.e. server sends an wild-card certificate).

-- 
Kazuho Oku

---

From: Martin Thomson <[email-masked]>
Date: Fri, 15 Jan 2016 16:24:43 +1100

I think that this would be a little less tortured if you accept that a
Cache-Digest header field is the primary or only source of truth.

The frame might then be dropped entirely, apart from one concern, below...

> a replace.  Or clients can simply close the H2 connection to clear the

The advantage that a frame provides is that it can be assumed to have
state shared between requests.  A header field cannot assume the same.
Therein lies the problem.

> domain.

A SW can speak for an entire origin (scheme+host+port), but I wouldn't
recommend it since it makes other operational aspects of SW more
challenging.  If you like, an optional parameter could be added to a
header field to describe the "scope" that the Cache-Digest covers.

---

From: Kazuho Oku <[email-masked]>
Date: Fri, 15 Jan 2016 15:23:05 +0900

> Cache-Digest header field is the primary or only source of truth.

Thank you for the suggestion.  That seems to be true.

Even though including hop-by-hop information as a HTTP header in
HTTP/2 seems strange, it will likely help people using cache digests.

Agreed.

Regarding the issue, it is possible to include cache digest in every
HTTP request while maintaining the bandwidth spent for sending the
digests almost on par with when using an HTTP/2 frame, by using the
following approach.

A client should:
1) include entire cache-digest in the first HTTP request sent over an
H2 connection
2) for following HTTP requests, send two (or more) cache-digest
headers, one should contain the value sent in step 1), other(s) should
contain only the values that changed after step 1)

A server should merge the values of the cache-digest headers to
construct the client's cache digest.

In this approach cache-digest header containing all GCS keys (which is
likely to be large) are repeatedly sent, we can expect that it would
be efficiently compressed by HPACK (likely will become 1 byte or 2
byte).

The deltas associated to the following HTTP requests will be small and
can effectively ignored in terms of consumed bandwidth.

One downside of the approach is that the cost of decoding the header
might become an issue on the server-side; but server implementations
can cache the decoded values of the digest.

Agreed.

-- 
Kazuho Oku

-- 
Kazuho Oku

---

From: Martin Thomson <[email-masked]>
Date: Fri, 15 Jan 2016 17:33:15 +1100

> construct the client's cache digest.

I don't think that this would be a good idea.  I would rather try to
use the "scope" parameter to narrow things down, to omit the header
field if there aren't changes, or rely on the server performing some
sort of "magic" tracking based on what it has sent.

The upshot of using a header field is that this really means that this
isn't hop-by-hop information.  Like Ilya said, I doubt that's a huge
problem.  Nothing stopping an intermediary from consuming, modifying,
or inserting the value though.

---

From: Martin Thomson <[email-masked]>
Date: Fri, 15 Jan 2016 17:57:41 +1100

On 15 January 2016 at 17:51, Kazuho Oku <[email-masked]> wrote:
> Considering the fact that such APIs (that are incapable of expressing
> states shared between the requests) exist, I think the semantics
> should be defined in a way that every HTTP request can be complete by
> itself if we are going to send cache digest as a header.

Yeah, I agree.  That doesn't preclude a mechanism that explicitly
creates and references state, but that might be too complex to manage.
Sending the whole table every time isn't great, but let's see if this
is successful enough to warrant further optimization first.

---

From: Kazuho Oku <[email-masked]>
Date: Fri, 15 Jan 2016 15:51:35 +0900

> sort of "magic" tracking based on what it has sent.

I doubt if the approach will work well with SW-based implement ion of
cache-digests (which is one of the reasons why we want it to be
defined as a header).

My understanding is that Fetch API does not expose to the caller if a
connection is being reused or if a new connection is established.
Therefore, unless it includes entire cache digest in every HTTP
request, there is a chance that the digest value will not be available
to the server (in case a new connection was established).

Please correct me if I misunderstand the Fetch API.  I am not familiar with it.

> or inserting the value though.

Thank you for the clarification.

I had thought that it is, considering the fact that intermediaries may
have cache associated to them (and thereby want to change the value of
the header).  However considering the fact that If-Modified-Since
header is not defined as a hop-by-hop header, now it seems to me that
you are correct.

-- 
Kazuho Oku

---

From: Stefan Eissing <[email-masked]>
Date: Fri, 15 Jan 2016 16:16:37 +0100

I just released v1.2.0 of mod_http2 with support for
the 'Cache-Digest' request header 
(https://github.com/icing/mod_h2/releases/tag/v1.2.0)

When a request comes with

Cache-Digest: BAqbuEx20N-JQDERYEeacOaLNC6x_wA=

the value is assumed to be base64url encoded bytes of cache digest as
described in the draft. (The implementation may have some bits wrong, but
it works with itself at least...)

There is also a handler that can be configured in the server that hands
out JSON with some stats and the cache digest of the current connection.
I use this for my own testing, might come in handy to have that available
to client experimenters. How-to in the release notes.

-Stefan

> Am 15.01.2016 um 07:57 schrieb Martin Thomson <[email-masked]>:
>

---

From: Martin Thomson <[email-masked]>
Date: Sat, 16 Jan 2016 11:50:31 +1100

That was fast. :)

> Cache-Digest: BAqbuEx20N-JQDERYEeacOaLNC6x_wA=

We should probably prohibit the use of padding, since that isn't valid
according to the grammar (unless it's inside a quoted string bit).

BTW, that form doesn't easily allow for parameters, such as
scope="/foo" without defining a custom grammar.  Talk to Julian about
how much he loves that.

---

From: Kazuho Oku <[email-masked]>
Date: Mon, 18 Jan 2016 09:14:17 +0900

> to client experimenters. How-to in the release notes.

Wow!  Very fast!  I'm delighted to hear that.

-- 
Kazuho Oku

---

From: Kazuho Oku <[email-masked]>
Date: Mon, 18 Jan 2016 09:48:19 +0900

Under the premise that we are switching to using an HTTP header, my
idea at the moment is to define it like:

  cache-diges = 1#cache-digest-element
  cache-digest-element = cache-digest-nv *(OWS ";" OWS cache-digest-nv)
  cache-digest-nv = fresh-nv / authority-nv

  fresh-nv = "fresh=" fresh-value
  fresh-value = BASE64 encoded GCS defined in h2-cache-digest-00
(without padding)

  authority-nv = "authority=" authority-value
  authority-value =
        authority              # defined in RFC3986
        / wildcard-identifier  # defined in RFC 6125 6.4.3

IMO the definition covers most of the requirements of / suggestions
provided by many, including the 4 points described below:

1. Cache-Digest can be expressed as a header

For example, a Cache-Digest containing https://example.com/style.jss
and https://example.com/jquery.js will look like:

  cache-digest: fresh=AQiZAh8

2. Effective compression with HPACK

A client can make the use of HPACK to effectively compress the
cache-digest that changes slightly per every HTTP request, by
splitting a cache-digest to two headers: a large header that does not
change per every request, and a small header that encodes the delta
from the large header.

For example, a client sends a large cache-digest header in the first
HTTP request after establishing an HTTP/2 connection.

  cache-digest: fresh=ABC...xyz      # very large header

In the following HTTP requests sent over the same connection, a client
will resend the header sent in the first connection, plus a second
header containing the delta from the first connection.

  cache-digest: fresh=ABC...xyz      # the same header as above
  cache-digest: fresh=ax9e           # digest containing delta from above

3. Specifying the scope

In case the server is known to be authoritative for the entire domain
(e.g. wildcard certificate is used), a client can send a cache-digest
for all the hosts under that domain, by specifying the `authority`
attribute.

  cache-digest: fresh=xxxxx; authority=*.example.com

4. Possibility to extend the header

There has been discussions if we should support an encoding other than
GCS, or how we can send cache-digests of a non-fresh resource.

The ABNF has a room to support digests other than just the `fresh`
digest (that represents the fresh resources within the cache using
GCS); for example, we can afterwards define an attribute named
`fresh-bf` for representing a digest of fresh resources using bloom
filter

  cache-digest: fresh-bf=...

or, we can define an attribute named `if-modified-since` to represent
a digest of non-fresh resources, once we agree how they should be
hashed.

  cache-digest: if-modified-since=...

-- 
Kazuho Oku

---

From: Martin Thomson <[email-masked]>
Date: Mon, 18 Jan 2016 17:19:43 +1100

> 3. Specifying the scope

We talked about narrowing the scope for SW, what abut that?

> 4. Possibility to extend the header

Yeah, I like this.  The generic parameter form can be used here to
support almost arbitrary extension through key-value pairs.

---

From: Kazuho Oku <[email-masked]>
Date: Mon, 18 Jan 2016 16:41:22 +0900

> We talked about narrowing the scope for SW, what abut that?

Sorry, I did not realize that what you requested was to limit the path
of the cache digest.

Should we define `path` attribute for the purpose?

-- 
Kazuho Oku

---

From: Martin Thomson <[email-masked]>
Date: Mon, 18 Jan 2016 20:02:49 +1100

> Should we define `path` attribute for the purpose?

Maybe.  SW calls it scope, which makes it tricky to map out.  Perhaps
renaming the other from scope to domain would be better.

---

From: Stefan Eissing <[email-masked]>
Date: Mon, 18 Jan 2016 11:19:37 +0100

+1 generic header parameter form.

If I may make a proposal:

Cache-Digest      = "Cache-Digest" ":" #digest-value
  digest-value    = "<" base64url encoded digest ">" *( ";" digest-param )
  digest-param    = ( ( "domain" "=" domain-value )
                  | ( "path" "=" path-value )
                  | ( "codec" "=" codec-value )
                  | ( "update" ( "=" update-param ) )
                  | ( digest-extension ) )
 
 digest-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] )

 domain-value     =
       authority              # defined in RFC3986
       | wildcard-identifier  # defined in RFC 6125 6.4.3

 path-value       = path-absolute    # defined in RFC3986 3.3

 codec            = ( "GCS-SHA256" | ( ptoken | quoted-string ) )

An endpoint seeing a cache-digest for an unknown codec should ignore the header. "GCS-SHA256" is the default codec, as described in...

"update" should indicate that the digest is intended as an update to (as opposed to replace) any cache information that an endpoint might already have. How delta digests are updated depends on the codec used. GCS-SHA256 defines only additions of new values.

If more than one Cache-Digest header is defined, header values are merged as defined for generic header parameter form, e.g. concatenation with "," as separator. When interpreting multiple digests, order is relevant in regard to "update" semantics.

 :authority/Host: example.com
 cache-digest: <AQiZAh8>

or, explicitly mentioning defaults:

 cache-digest: <AQiZAh8>;codec=GCS-SHA256;domain="example.com";path="/"

which, again, is equivalent to 

 cache-digest: <AQiZAh8>;codec="GCS-SHA256";domain=example.com;path=/

I am not keen on the exact names and wordings here...

As to "domain" and "path", I can certainly understand the need for "domain". How exactly "path" should/needs to be honored by an endpoint, is not clear to me. I tend to lean in the direction to ignoring "path" in my implementation. Is that safe with what usage you have in mind with this?

-Stefan

> Am 18.01.2016 um 10:02 schrieb Martin Thomson <[email-masked]>:
>

---

From: Julian Reschke <[email-masked]>
Date: Mon, 18 Jan 2016 11:32:30 +0100

>   codec            = ( "GCS-SHA256" | ( ptoken | quoted-string ) )

I wouldn't use angle brackets here; let's leave them for use in places 
where the value is a URI (Link header field and several WebDAV header 
fields).

Just us token/quoted-sting here.

Also, do not wire parameter names into the ABNF; this mixes syntax with 
semantics.

Best regards, Julian

---

From: Kazuho Oku <[email-masked]>
Date: Mon, 18 Jan 2016 23:09:26 +0900

> renaming the other from scope to domain would be better.

Thank you for the response.

Under the premise that we should be two attributes, one matches
against authority and one matches against path, I wonder if the former
should be named `domain`.

The positive side of naming it `domain` is that the name is the same
as that used with cookies.  The negative side is that the semantics
will not be the same as the cookies.

IMO we should have three types of authority-level scopes for a cache
digest: a) exact match of the authority (e.g. host:port), b) exact
match of the host, c) match against a wildcard certificate.  b) and c)
come from the definitions in RFC 7540 Section 10.1.  I also believe
that a) should be the default.

However, the three types do not map well against the Domain attribute
of Cookie.  Exact match only exists for b), and that is when the
Domain attribute is not used (however if we are to use make a) as the
default, we need to have the attribute defined in this case).

To summarize, even though the role is similar to the Domain attribute
of Cookie, we need to define totally different semantics for the
attribute for Cache Digest; and I am afraid re-using `Domain` might
cause confusion.

That said, I propose using the name `host`, with following semantics.

* if `host` attribute is not specified, the scope of the cache digest
is the authority
* if a non-wildcard `host` attribute is specified, the scope is the
host.  The value MUST be equal to the host part of the :authority
pseudo header
* if a wildcard `host` attribute is specified, the scope is the hosts
that match the wildcard.  The value MUST be equal to that provided by
the TLS certificate

-- 
Kazuho Oku

---

From: Kazuho Oku <[email-masked]>
Date: Mon, 18 Jan 2016 23:46:34 +0900

2016-01-18 19:32 GMT+09:00 Julian Reschke <[email-masked]>:
> I wouldn't use angle brackets here; let's leave them for use in places where
> the value is a URI (Link header field and several WebDAV header fields).
>

Thank you for the advice.

Obviously I have used an old-fashioned RFC as a reference when writing
the ABNF (that Stefan probably modified).  I should have looked into a
newer one, such as the Alt-Svc draft.

-- 
Kazuho Oku

---

From: Stefan Eissing <[email-masked]>
Date: Wed, 20 Jan 2016 13:57:46 +0100

I make another attempt, based on the feedback (Kazuho, please stop me if you prefer to work on this):

digest-value    = base64url-encoded-digest *( ";" digest-param )
digest-param    = name "=" value
name            = token
value           = token | quoted-string

base64url-encoded-digest is the binary output of the cache digest codec, 
encoded as a base64url string (that is, the URL- and filename-safe 
Base64 encoding described in Section 5 of [RFC4648], with any trailing 
'=' characters omitted). 

The following digest parameter are defined:
"domain"   value is authority, as defined in RFC3986, or 
          wildcard-identifiers, as defined in RFC 6125 6.4.3. 
          The implicit default for the domain value is "*".
"path"     value is a path-absolute, as defined in RFC3986 3.3.
          The implicit default for the path value is "/".
"codec"    the identifier of the algorithm used to calculate
          the cache digest.
          The implicit default for codec is "GCS-SHA256".

Digest from a Cache-Digest header always replace any matching 
existing digests. Multiple digests in one request are additive. This
allows additions to earlier digests in a form, which can be 
beneficial when using HTTP/2 header compression.

A digest replaces any existing digest if its domain parameter
matches the domain of an existing digest (with '*' matching any
domain) and the path parameter is a prefix of or equal to the
existing path.

If the codec of a digest is unknown, the digest MUST be assumed
to be empty and SHOULD replace any matching existing digests.

A candidate of a server push is matched against a cache digest
by comparing its :authority against the digest domain (port number?)
and its :path against the digest path (equal or prefix). Only
then is the candidate matched against the digest content.

> Am 18.01.2016 um 15:46 schrieb Kazuho Oku <[email-masked]>:
>

---

From: Martin Thomson <[email-masked]>
Date: Thu, 21 Jan 2016 09:44:10 +1100

>           The implicit default for the domain value is "*".

Wildcards in 6125 have the '*' match just a single label.  Whereas I'm
guessing that the '*' you have here is supposed to match all labels.

>           The implicit default for the path value is "/".

You need to actually say that this is a prefix and that all resources
that have a path-absolute value that starts with this value are
implicitly covered.

>           The implicit default for codec is "GCS-SHA256".

Looks good.

---

From: Kazuho Oku <[email-masked]>
Date: Thu, 21 Jan 2016 10:26:39 +0900

> I make another attempt, based on the feedback (Kazuho, please stop me if you prefer to work on this):

Sorry.  I thought I had responded to your email.  It has always been
my pleasure (and fun) to discuss how we should design and implement
cache digests.

I think we mostly agree on how the header should be designed.

Below, I will cover the semantic differences between yours and mine
(laid out in https://lists.w3.org/Archives/Public/ietf-http-wg/2016JanMar/0120.html),
and my thoughts.

> digest-value    = base64url-encoded-digest *( ";" digest-param )

I would like to have the encoded digest stored as a name-value pair,
thereby allowing the client to store several types of digests within a
single digest-value.

At the moment, the draft only covers how the digests of fresh
resources in cache should be encoded.  However if all goes well, I
anticipate that we would extend the spec., to support stale resources
as well.  For such purpose, being able to store more than one type of
digests in a single digest-value would make the header less redundant.

As an example, the header conveying a digest for fresh resources and a
digest for stale resources with if-modified-since headers can look
like:

  cache-digest: fresh=xxxxx; if-modified-since=yyyyy; authority=*.example.com

only in case if we use name-value pairs to encode the digest.

> domain) and the path parameter is a prefix of or equal to the

I believe that a server should be able to determine the domain(s) the
client considers the server to be authoritative for.

Therefore, I think that a) the default scope should be the value
passed in by the `:authority` pseudo header, and b) otherwise the
client should explicitly specify the scope using the language defined
in RFC 6125 6.4.3.

Unless the scope is transferred over HTTP, HTTP servers need to have
the knowledge of the common name in the TLS certificate being used.
However that might not always be easy considering the fact that in
some cases TLS terminators (run by a different entity) might be placed
in front of the HTTP server, or in case we want to rolling-update
certificates of a TLS terminator cluster from a host-level certificate
to a wildcard certificate.

Requiring the client to always specify the scope of the digest aligns
with the fact that in HTTP/1.1 and HTTP/2 specifications the Host
header (or the :authority header) is transferred even if TLS
certificate is used (and the fact that not the common name stored in
the certificate but the value of the header IS used as the name of the
authority).

> to be empty

If the codec of a digest is unknown, a server must behave as if it did
not receive that digest.

Assuming it to be empty would mean that a server will consider the
cache of the client to be empty.  It should mean to the server that
the client's cache state is unknown (for the category of the resources
(e.g. fresh)).

And this definition is important for extending the spec., to cover
stale resources.  For example, if a client sends a digest of fresh
resources and a digest of stale resources, a server knowing only how
to handle the former should respect the former and ignore the latter.

Agreed.

-- 
Kazuho Oku

---

From: Stefan Eissing <[email-masked]>
Date: Thu, 21 Jan 2016 12:08:45 +0100

Kazuho,

thanks for your detailed feedback. I see that you have things in mind for cache digest that I had not understood. 

I would still argue for the standalone digest form and define a new parameter that specifies the digest type. For example:
   Cache-Digest: base64urldigest;type=fresh, base64digest;type=if-modified-since
with a default of "fresh".

To your other points, I agree.

> Am 21.01.2016 um 02:26 schrieb Kazuho Oku <[email-masked]>:
>

---

From: Richard Bradbury <[email-masked]>
Date: Fri, 22 Jan 2016 17:47:10 +0000

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...

I therefore support the idea of including version information in the 
client-generated cache digest. 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. Naively, my first thought was that 
versioning metadata (last-modified and/or etag) in the pushed HEADERS 
frame should be enough to convince a sensible client otherwise. But then 
I realised that the client may already have rejected the PUSH_PROMISE 
before it even gets to see the pushed HEADERS. One slightly wacky idea 
would be for the server to include the up-to-date modification datestamp 
and/or entity tag values in its PUSH_PROMISE frame (i.e. the simulated 
client request) as (respectively) if-modified-since and if-none-match 
request headers. I'm not sure even this would be enough to invalidate 
the client's cache entry though. Hmm... Any better ideas?

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?

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 :-)

On Tue, 12 Jan 2016 10:04:00 +0900 Kazuho Oku <[email-masked]> wrote:
> 2016-01-11 2:11 GMT+09:00 Alcides Viamontes E <[email-masked] 
> <mailto:[email-masked]?Subject=Re%3A%20Submitted%20new%20I-D%3A%20Cache%20Digests%20for%20HTTP%2F2&In-Reply-To=%3CCANatvzxcKS46iAqAdfBHuWPt5k3XkR79NDMPPtDakOb2jPAywA%40mail.gmail.com%3E&References=%3CCANatvzxcKS46iAqAdfBHuWPt5k3XkR79NDMPPtDakOb2jPAywA%40mail.gmail.com%3E>>:
> > ...

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.

> version information as well.

Kind regards,

-- 
Richard.

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 27 Jan 2016 10:11:47 +0900

Stefan,

Thank you for your response.

> with a default of "fresh".

To me it seems that we have a trade-off between simplicity and
possibly better efficiency in future extensions.  Considering the fact
that we cannot tell for certain what would happen in the future, I
will not argue for making the digest value as a parameter.

Reflecting your ideas, an early draft that reflects my understanding
can be found at https://gist.github.com/kazuho/238de4ab8a964c9c927c
(FYI the source code is https://github.com/mnot/I-D/pull/167).  Please
note the version on the Gist reflect my ideas only; Mark might want to
make modifications before updating the I-D.

That said, it would be great if you could see it looks fine.

FWIW I see two potential pitfalls when _upgrading_ to this draft:

* size of `N` and `P` has been changed from 8 bits to 5 bits

* scope is expressed via parameters named `host` and `path`

Note that the former is not named `domain`.  Please refer to
https://lists.w3.org/Archives/Public/ietf-http-wg/2016JanMar/0132.html
for the reason behind.

-- 
Kazuho Oku

---

From: Martin Thomson <[email-masked]>
Date: Wed, 27 Jan 2016 12:24:22 +1100

> host.  The value MUST be equal to the host part of the :authority

This prevents someone from connecting to an HTTP/2 server that
supports multiple names and making assertions about multiple of those
names.  For instance, this seems perfectly reasonable to send to a
server that has a cert for example.com and foo.example...

GET / HTTP/1.1
Host: example.com
Cache-Digest: CgRSlw, soOIs;host=foo.example

After all, you want to suppress pushes from foo.example.

(Note that the origin frame might help advise what origins you want to
cover here.)

---

From: Eliezer Croitoru <[email-masked]>
Date: Wed, 27 Jan 2016 04:45:29 +0200

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.

> 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?

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.
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.
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.
- 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

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 27 Jan 2016 13:38:33 +0900

2016-01-23 2:47 GMT+09:00 Richard Bradbury <[email-masked]>:
> 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...
>
> client-generated cache digest.

Thank you for your feedback.  I am very happy to see interest in
including the version information in cache digest.

>  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.

> 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.

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.

> 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.
>

-- 
Kazuho Oku

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 27 Jan 2016 15:35:44 +0900

> server that has a cert for example.com and foo.example...

Thank you for the comment.  I have updated my draft to:

```
The "host" parameter modifies the scope of the digest-value.

If present, the scope of the digest-value is the resources belonging
to the specified host. If "https" scheme is used, the parameter MAY
contain a wildcard character "*". In such case, the scope of the
digest-value is the resources belonging to all the hosts that match
against the value of the parameter as defined in [RFC2818],
Section 3. The value SHOULD be a subset of the hosts the server is
authoritative of.
```

-- 
Kazuho Oku