RE: Server Push and Content Negotiation

(From memory, without consulting devs)

On the server-side, there's an additional complication you're eliding over here -- the push may be cross-app, even on the same origin.  Say you have an app that dynamically renders things, but pushes other resources that are handled by a static file handler elsewhere in the system's request routing structure.  We essentially punt this choice to the app -- the app sends us a set of request headers.  We then simultaneously dispatch the PUSH_PROMISE on the wire and begin routing that request as if it had just been received from the client.  So whatever negotiation headers are specified by the app are those which will be seen by the recipient of the pushed request.  Requiring that we inspect the response to the push *before constructing* the request would require a total rearchitecting -- essentially requiring that the app hand us a fully-formed response and only then being able to declare the push.  That's going to delay processing of the initial request, which we really don't want blocked.

On the client side, there are two stages -- first, we compare the pushed request with the cache to see if we would have been able to serve that request out of cache.  If so, we reset the stream.  Otherwise, we receive and hold the content.  When the browser makes a request, we perform the same checks of whether the browser's request could be served by the held response.  So yes -- Vary is definitely necessary.

-----Original Message-----
From: Mark Nottingham [] 
Sent: Tuesday, August 23, 2016 9:27 PM
To: HTTP Working Group <>
Subject: Server Push and Content Negotiation

The interaction of Content Negotiation and Server Push isn't really specified. Depending on how it's implemented, it could be quite tricky, because it seemingly requires the server to guess what the client would have sent, in order to negotiate upon it.

However, it becomes much simpler if we assume that the client SHOULD NOT check a `PUSH_PROMISE` request's headers to see whether or not it would have sent that request.

This means, for example, that if you `PUSH_PROMISE` the "wrong" `User-Agent`, `Accept-Encoding`, `User-Agent` or even `Cookie` header field, the client SHOULD still use the pushed response; all they're looking for is a matching request method and URL.

However, this does imply a few things:

* The pushed request and response MUST still "agree"; i.e., if you're using gzip encoding, `Accept-Encoding` and `Content-Encoding` should be pushed with appropriate values.
* The pushed response MUST have an appropriate `Vary` header field, if it is cacheable. This is so that the cache operates properly.

Additionally, the server needs to know what the base capabilities and preferences of the client are, to allow it to select the appropriate responses to push. To aid this, servers SHOULD create a PUSH_PROMISE's request by copying the values of the request header fields mentioned in the `Vary` response header field from the request that is identified by the `PUSH_PROMISE` frame's Stream ID.

So, for example, if the first request for a page had the following headers:

:method: GET
:scheme: https
:path: /
User-Agent: FooAgent/1.0
Accept-Encoding: gzip, br
Accept-Language: en, fr
Accept: text/html,s application/example, image/*
Cookie: abc=123

and the server wishes to push these response headers for `/style.css`:

:status: 200
Content-Type: text/css
Cache-Control: max-age=3600
Content-Encoding: gzip
Vary: Accept-Encoding

then it should use these headers for the `PUSH_PROMISE`:

:method: GET
:scheme: https
:path: /style.css
Accept-Encoding: gzip, br

This approach has its limits. For example, use of Client Hints might not be practical with server push (since in some circumstances, hints might change between the base page request and the request for what's been pushed).

I'd be tempted to go even further and say that PUSH_PROMISE headers SHOULD NOT contain `DNT`, `User-Agent`, `Cookie` or similar headers UNLESS they were specified in Vary.

What do people think -- is this worth specifying? How are implementations currently doing this? 

Mark Nottingham

Received on Wednesday, 24 August 2016 18:41:25 UTC