Server Push and Conditional Requests

Mark Nottingham <mnot@mnot.net> Wed, 24 August 2016 05:21 UTC

Return-Path: <ietf-http-wg-request+bounce-httpbisa-archive-bis2juki=lists.ie@listhub.w3.org>
X-Original-To: ietfarch-httpbisa-archive-bis2Juki@ietfa.amsl.com
Delivered-To: ietfarch-httpbisa-archive-bis2Juki@ietfa.amsl.com
Received: from localhost (localhost [127.0.0.1]) by ietfa.amsl.com (Postfix) with ESMTP id DBACE128E19 for <ietfarch-httpbisa-archive-bis2Juki@ietfa.amsl.com>; Tue, 23 Aug 2016 22:21:22 -0700 (PDT)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -7.469
X-Spam-Level:
X-Spam-Status: No, score=-7.469 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, HEADER_FROM_DIFFERENT_DOMAINS=0.001, RCVD_IN_DNSWL_HI=-5, RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, RP_MATCHES_RCVD=-0.548, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001] autolearn=ham autolearn_force=no
Received: from mail.ietf.org ([4.31.198.44]) by localhost (ietfa.amsl.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id xXRUTr7tFnYK for <ietfarch-httpbisa-archive-bis2Juki@ietfa.amsl.com>; Tue, 23 Aug 2016 22:21:21 -0700 (PDT)
Received: from frink.w3.org (frink.w3.org [128.30.52.56]) (using TLSv1.2 with cipher DHE-RSA-AES128-SHA (128/128 bits)) (No client certificate requested) by ietfa.amsl.com (Postfix) with ESMTPS id 5AC97127077 for <httpbisa-archive-bis2Juki@lists.ietf.org>; Tue, 23 Aug 2016 22:21:21 -0700 (PDT)
Received: from lists by frink.w3.org with local (Exim 4.80) (envelope-from <ietf-http-wg-request@listhub.w3.org>) id 1bcPmM-0003Z6-J2 for ietf-http-wg-dist@listhub.w3.org; Wed, 24 Aug 2016 04:27:06 +0000
Resent-Date: Wed, 24 Aug 2016 04:27:06 +0000
Resent-Message-Id: <E1bcPmM-0003Z6-J2@frink.w3.org>
Received: from lisa.w3.org ([128.30.52.41]) by frink.w3.org with esmtps (TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.80) (envelope-from <mnot@mnot.net>) id 1bcPmC-0003Xk-4P for ietf-http-wg@listhub.w3.org; Wed, 24 Aug 2016 04:26:56 +0000
Received: from mxout-07.mxes.net ([216.86.168.182]) by lisa.w3.org with esmtps (TLS1.2:DHE_RSA_AES_256_CBC_SHA256:256) (Exim 4.80) (envelope-from <mnot@mnot.net>) id 1bcPm9-0000Wr-Ls for ietf-http-wg@w3.org; Wed, 24 Aug 2016 04:26:55 +0000
Received: from [192.168.3.104] (unknown [124.189.98.244]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by smtp.mxes.net (Postfix) with ESMTPSA id 61BE822E1F3 for <ietf-http-wg@w3.org>; Wed, 24 Aug 2016 00:26:30 -0400 (EDT)
From: Mark Nottingham <mnot@mnot.net>
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: quoted-printable
Message-Id: <FCF25267-9CE8-4DDB-A0B3-29C23F81124E@mnot.net>
Date: Wed, 24 Aug 2016 14:26:27 +1000
To: HTTP Working Group <ietf-http-wg@w3.org>
Mime-Version: 1.0 (Mac OS X Mail 9.3 \(3124\))
X-Mailer: Apple Mail (2.3124)
Received-SPF: pass client-ip=216.86.168.182; envelope-from=mnot@mnot.net; helo=mxout-07.mxes.net
X-W3C-Hub-Spam-Status: No, score=-8.3
X-W3C-Hub-Spam-Report: AWL=1.351, BAYES_00=-1.9, RCVD_IN_DNSWL_LOW=-0.7, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001, W3C_AA=-1, W3C_DB=-1, W3C_IRA=-1, W3C_IRR=-3, W3C_WL=-1
X-W3C-Scan-Sig: lisa.w3.org 1bcPm9-0000Wr-Ls 41d23499d604843f02b144578de89985
X-Original-To: ietf-http-wg@w3.org
Subject: Server Push and Conditional Requests
Archived-At: <http://www.w3.org/mid/FCF25267-9CE8-4DDB-A0B3-29C23F81124E@mnot.net>
Resent-From: ietf-http-wg@w3.org
X-Mailing-List: <ietf-http-wg@w3.org> archive/latest/32337
X-Loop: ietf-http-wg@w3.org
Resent-Sender: ietf-http-wg-request@w3.org
Precedence: list
List-Id: <ietf-http-wg.w3.org>
List-Help: <http://www.w3.org/Mail/>
List-Post: <mailto:ietf-http-wg@w3.org>
List-Unsubscribe: <mailto:ietf-http-wg-request@w3.org?subject=unsubscribe>

Thinking about how Server Push interacts with conditional requests <http://httpwg.org/specs/rfc7232.html>, I can see three interesting modes of operation, outlined below.


1. If-Match / If-Unmodified-Since

If the server has immediate access to the response being pushed (e.g., if the server is authoritative for it, or it is fresh in cache), it might want to send conditional headers in the `PUSH_PROMISE` request, to give the client the earliest possible chance to send a `RST_STREAM` (since the promised stream might be delayed due to priorities, etc.).

The most obvious way to do this with existing headers is using `If-Match` and/or `If-Unmodified-Since`; if the validator matches what the client has, it can send a `RST_STREAM`; otherwise, they can wait for the full response (which will presumably have validators matching those sent in the request).

~~~
:method: GET
:scheme: https
:authority: www.example.com
:path: /images/1234.jpg
Host: www.example.com
If-Match: "abcdef"
~~~

Here, when a client receives these headers in a `PUSH_PROMISE`, it can send a `RST_STREAM` if it has a fresh cached response for `https://www.example.com/images/1234.jpg` with the `ETag` "abcdef". If it does not do so, the server will continue to push the successful (`2xx`) response (since the `ETag` does in fact match what is pushed).


2.  If-None-Match / If-Modified-Since

If the server does not have a fresh local copy of the response, but does have access to a stale one (in the meaning of RFC7234), it can `PUSH_PROMISE` with `If-None-Match` and/or `If-Modified-Since`:

~~~
:method: GET
:scheme: https
:authority: www.example.com
:path: /images/5678.jpg
Host: www.example.com
If-None-Match: "lmnop"
~~~

That way, the client again has an opportunity to send `RST_STREAM` if it already has a fresh copy with that validator in cache. 

Once the server has obtained a fresh (possibly validated) response, it can either push a `304 (Not Modified)` response in the case that the `ETag` hasn't changed, or a successful (`2xx`) response if it has.

Note that if the client has a fresh copy in cache, but the server does not, the client can still use the fresh copy; it has not been invalidated just because the server has not kept its copy fresh.


3. 304 (Not Modified) without a Conditional

If the server believes that the client does have a stale but valid copy in its cache (e.g., through the use of a cache digest), it can send a `PUSH_PROMISE` followed by a pushed `304 (Not Modified)` response to revalidate that cached response, thereby making it fresh in the client's cache.

If the server has a local copy of the response that it wishes to use, it can send the PUSH_PROMISE with an `If-None-Match` and/or `If-Modified-Since` conditional, as above. 

However, if it does not, it will still be desirable to generate the `PUSH_PROMISE` as soon as possible, so as to avoid the race described in RFC7540, Section 8.2.1.

To allow this, a request without a conditional can be sent:

~~~
:method: GET
:scheme: https
:authority: www.example.com
:path: /images/9012.jpg
Host: www.example.com
~~~

When the response body is available to the server, it can send a `304 (Not Modified)` if it believes that the client already holds a copy (fresh or stale); however, it MUST include the validators to allow the client to confirm this. For example:

~~~
:status: 304
ETag: "abc123"
Date: Tue, 3 Sep 2016 04:34:12 GMT
Content-Type: image/jpeg
Cache-Control: max-age=3600
~~~

In this case, if the client's cached response does not have the same `ETag` it SHOULD make a request to obtain a fresh response.

On the other hand, if the server determines that the client does not have the appropriate cached response, it can send the full, successful (`2xx`) response:

~~~
:status: 200
ETag: "abc123"
Date: Tue, 3 Sep 2016 04:34:12 GMT
Content-Type: image/jpeg
Cache-Control: max-age=3600

[ body ]
~~~

Note that this approach relies upon an _implicit conditional_ in the PUSH_PROMISE request. If felt necessary, this can be made explicit, for example by defining a new conditional header `If-In-Digest`.



Which of these do people think are interesting to specify, implement and use? Are there problems, or other approaches we should be considering?

Cheers,

--
Mark Nottingham   https://www.mnot.net/