Server Push and Content Negotiation

Mark Nottingham <> Wed, 24 August 2016 05:23 UTC

Return-Path: <>
Received: from localhost (localhost []) by (Postfix) with ESMTP id 7EEFE127077 for <>; Tue, 23 Aug 2016 22:23:48 -0700 (PDT)
X-Virus-Scanned: amavisd-new at
X-Spam-Flag: NO
X-Spam-Score: -7.469
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 ([]) by localhost ( []) (amavisd-new, port 10024) with ESMTP id NDvvcq_Zx5bZ for <>; Tue, 23 Aug 2016 22:23:47 -0700 (PDT)
Received: from ( []) (using TLSv1.2 with cipher DHE-RSA-AES128-SHA (128/128 bits)) (No client certificate requested) by (Postfix) with ESMTPS id 06D0B128E19 for <>; Tue, 23 Aug 2016 22:23:47 -0700 (PDT)
Received: from lists by with local (Exim 4.80) (envelope-from <>) id 1bcPmR-0003Zb-SF for; Wed, 24 Aug 2016 04:27:11 +0000
Resent-Date: Wed, 24 Aug 2016 04:27:11 +0000
Resent-Message-Id: <>
Received: from ([]) by with esmtps (TLS1.2:DHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.80) (envelope-from <>) id 1bcPmC-0003Xl-9P for; Wed, 24 Aug 2016 04:26:56 +0000
Received: from ([]) by with esmtps (TLS1.2:DHE_RSA_AES_256_CBC_SHA256:256) (Exim 4.80) (envelope-from <>) id 1bcPmA-0006Rj-Eb for; Wed, 24 Aug 2016 04:26:55 +0000
Received: from [] (unknown []) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by (Postfix) with ESMTPSA id 00AF522E1FA for <>; Wed, 24 Aug 2016 00:26:32 -0400 (EDT)
From: Mark Nottingham <>
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: quoted-printable
Message-Id: <>
Date: Wed, 24 Aug 2016 14:26:31 +1000
To: HTTP Working Group <>
Mime-Version: 1.0 (Mac OS X Mail 9.3 \(3124\))
X-Mailer: Apple Mail (2.3124)
Received-SPF: pass client-ip=;;
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: 1bcPmA-0006Rj-Eb f067172a2cb2edd871599312833706d8
Subject: Server Push and Content Negotiation
Archived-At: <>
X-Mailing-List: <> archive/latest/32338
Precedence: list
List-Id: <>
List-Help: <>
List-Post: <>
List-Unsubscribe: <>

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