HTTP Working Group | M. Nottingham |
Internet-Draft | Fastly |
Updates: 7234 (if approved) | August 6, 2024 |
Intended status: Standards Track | |
Expires: February 7, 2025 |
This specification introduces an alternative way to select a HTTP response from a cache based upon its request headers, using the HTTP "Variants" and "Variant-Key" response header fields. Its aim is to make HTTP proactive content negotiation more cache-friendly.¶
This note is to be removed before publishing as an RFC.¶
Status information for this document may be found at <https://datatracker.ietf.org/doc/draft-ietf-httpbis-variants/>.¶
Discussion of this document takes place on the HTTP Working Group mailing list (<mailto:ietf-http-wg@w3.org>), which is archived at <https://lists.w3.org/Archives/Public/ietf-http-wg/>. Working Group information can be found at <https://httpwg.org/>.¶
Source for this draft and an issue tracker can be found at <https://github.com/httpwg/http-extensions/labels/variants>.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as “work in progress”.¶
This Internet-Draft will expire on February 7, 2025.¶
Copyright (c) 2024 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
HTTP proactive content negotiation ([RFC7231], Section 3.4.1) is seeing renewed interest, both for existing request headers like Accept-Language and for newer ones (for example, see [I-D.ietf-httpbis-client-hints]).¶
Successfully reusing negotiated responses that have been stored in a HTTP cache requires establishment of a secondary cache key ([RFC7234], Section 4.1). Currently, the Vary header ([RFC7231], Section 7.1.4) does this by nominating a set of request headers. Their values collectively form the secondary cache key for a given response.¶
HTTP's caching model allows a certain amount of latitude in normalising those request header field values, so as to increase the chances of a cache hit while still respecting the semantics of that header. However, normalisation is not formally defined, leading to infrequent implementation in cache, and divergence of behaviours when it is.¶
Even when the headers' semantics are understood, a cache does not know enough about the possible alternative representations available on the origin server to make an appropriate decision.¶
For example, if a cache has stored the following request/response pair:¶
GET /foo HTTP/1.1 Host: www.example.com Accept-Language: en;q=0.5, fr;q=1.0
HTTP/1.1 200 OK Content-Type: text/html Content-Language: en Vary: Accept-Language Transfer-Encoding: chunked [English content]
Provided that the cache has full knowledge of the semantics of Accept-Language and Content-Language, it will know that an English representation is available and might be able to infer that a French representation is not available. But, it does not know (for example) whether a Japanese representation is available without making another request, incurring possibly unnecessary latency.¶
This specification introduces the HTTP Variants response header field (Section 2) to enumerate the available variant representations on the origin server, to provide clients and caches with enough information to properly satisfy requests -- either by selecting a response from cache or by forwarding the request towards the origin -- by following the algorithm defined in Section 4.¶
Its companion Variant-Key response header field (Section 3) indicates the applicable key(s) that the response is associated with, so that it can be reliably reused in the future. Effectively, it allows the specification of a request header field to define how it affects the secondary cache key.¶
When this specification is in use, the example above might become:¶
GET /foo HTTP/1.1 Host: www.example.com Accept-Language: en;q=0.5, fr;q=1.0
HTTP/1.1 200 OK Content-Type: text/html Content-Language: en Vary: Accept-Language Variants: accept-language;de;en;jp Variant-Key: en Transfer-Encoding: chunked [English content]
Proactive content negotiation mechanisms that wish to be used with Variants need to define how to do so explicitly; see Section 6. As a result, it is best suited for negotiation over request headers that are well-understood.¶
Variants also works best when content negotiation takes place over a constrained set of representations; since each variant needs to be listed in the header field, it is ill-suited for open-ended sets of representations.¶
Variants can be seen as a simpler version of the Alternates header field introduced by [RFC2295]; unlike that mechanism, Variants does not require specification of each combination of attributes, and does not assume that each combination has a unique URL.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
This specification uses the Augmented Backus-Naur Form (ABNF) notation of [RFC5234] but relies on Structured Headers from [I-D.ietf-httpbis-header-structure] for parsing.¶
The Variants HTTP response header field indicates what representations are available for a given resource at the time that the response is produced, by enumerating the request header fields that it varies on, along with a representation of the values that are available for each.¶
Variants is a Structured Header Dictionary (Section 3.2 of [I-D.ietf-httpbis-header-structure]). Its ABNF is:¶
Variants = sh-dict
Each member-name represents the field-name of a request header that is part of the secondary cache key; each member-value is an inner-list of strings or tokens that convey representations of potential values for that header field, hereafter referred to as "available-values".¶
If Structured Header parsing fails or a member's value does not have the structure outlined above, the client MUST treat the representation as having no Variants header field.¶
Note that an available-value that is a token is interpreted as a string containing the same characters, and vice versa.¶
So, given this example header field:¶
Variants: accept-encoding=(gzip)
a recipient can infer that the only content-coding available for that resource is "gzip" (along with the "identity" non-encoding; see Appendix A.2).¶
Given:¶
Variants: accept-encoding=()
a recipient can infer that no content-codings (beyond identity) are supported. Note that as always, field-name is case-insensitive.¶
A more complex example:¶
Variants: accept-encoding=(gzip br), accept-language=(en fr)
Here, recipients can infer that two content-codings in addition to "identity" are available, as well as two content languages. Note that, as with all Structured Header dictionaries, they might occur in the same header field or separately, like this:¶
Variants: accept-encoding=(gzip brotli) Variants: accept-language=(en fr)
The ordering of available-values is significant, as it might be used by the header's algorithm for selecting a response (in this example, the first language is the default; see Appendix A.3).¶
The ordering of the request header fields themselves indicates descending application of preferences; in the example above, a cache that has all of the possible permutations stored will honour the client's preferences for Accept-Encoding before honouring Accept-Language.¶
Origin servers SHOULD consistently send Variant header fields on all cacheable (as per [RFC7234], Section 3) responses for a resource, since its absence will trigger caches to fall back to Vary processing.¶
Likewise, servers MUST send the Variant-Key response header field when sending Variants, since its absence means that the stored response will not be reused when this specification is implemented.¶
RFC EDITOR: Please remove the next paragraph before publication. ¶
Implementations of drafts of this specification MUST implement an HTTP header field named "Variants-##" instead of the "Variants" header field specified by the final RFC, with "##" replaced by the draft number being implemented. For example, implementations of draft-ietf-httpbis-variants-05 would implement "Variants-05".¶
This specification updates [RFC7234] to allow caches that implement it to ignore request header fields in the Vary header for the purposes of secondary cache key calculation ([RFC7234], Section 4.1) when their semantics are implemented as per this specification and their corresponding response header field is listed in Variants.¶
If any member of the Vary header does not have a corresponding variant that is understood by the implementation, it is still subject to the requirements there.¶
See Section 5.1.3 for an example.¶
In practice, implementation of Vary varies considerably. As a result, cache efficiency might drop considerably when Variants does not contain all of the headers referenced by Vary, because some implementations might choose to disable Variants processing when this is the case.¶
The Variant-Key HTTP response header field identifies one or more sets of available-values that identify the secondary cache key(s) that the response it occurs within are associated with.¶
Variant-Key is a Structured Header List (Section 3.1 of [I-D.ietf-httpbis-header-structure]) whose members are inner-lists of strings or tokens. Its ABNF is:¶
Variant-Key = sh-list
Each member MUST be an inner-list, and MUST itself have the same number of members as there are members of the representation's Variants header field. If not, the client MUST treat the representation as having no Variant-Key header field.¶
Each member identifies a list of available-values corresponding to the header field-names in the Variants header field, thereby identifying a secondary cache key that can be used with this response. These available-values do not need to explicitly appear in the Variants header field; they can be interpreted by the algorithm specific to processing that field. For example, Accept-Encoding defines an implicit "identity" available-value (Appendix A.2).¶
Each inner-list member is treated as identifying an available-value for the corresponding variant-axis' field-name. Any list-member that is a token is interpreted as a string containing the same characters.¶
For example:¶
Variants: accept-encoding=(gzip br), accept-language=(en fr) Variant-Key: (gzip fr)
This header pair indicates that the representation has a "gzip" content-coding and "fr" content-language.¶
If the response can be used to satisfy more than one request, they can be listed in additional members. For example:¶
Variants: accept-encoding=(gzip br), accept-language=(en fr) Variant-Key: (gzip fr), ("identity" fr)
indicates that this response can be used for requests whose Accept-Encoding algorithm selects "gzip" or "identity", as long as the Accept-Language algorithm selects "fr" -- perhaps because there is no gzip-compressed French representation.¶
When more than one Variant-Key value is in a response, the first one present MUST correspond to the request that caused that response to be generated. For example:¶
Variants: accept-encoding=(gzip br), accept-language=(en fr) Variant-Key: (gzip fr), (identity fr), (br fr oops)
is treated as if the Variant-Key header were completely absent, which will tend to disable caching for the representation that contains it.¶
Note that in¶
Variant-Key: (gzip fr) Variant-Key: ("gzip " fr)
The whitespace after "gzip" in the first header field value is excluded by the parsing algorithm, but the whitespace in the second header field value is included by the string parsing algorithm. This will likely cause the second header field value to fail to match client requests.¶
RFC EDITOR: Please remove the next paragraph before publication. ¶
Implementations of drafts of this specification MUST implement an HTTP header field named "Variant-Key-##" instead of the "Variant-Key" header field specified by the final RFC, with "##" replaced by the draft number being implemented. For example, implementations of draft-ietf-httpbis-variants-05 would implement "Variant-Key-05".¶
Caches that implement the Variants header field and the relevant semantics of the field-names it contains can use that knowledge to either select an appropriate stored representation, or forward the request if no appropriate representation is stored.¶
They do so by running this algorithm (or its functional equivalent) upon receiving a request:¶
Given incoming-request (a mapping of field-names to field-values, after being combined as allowed by Section 3.2.2 of [RFC7230]), and stored-responses (a list of stored responses suitable for reuse as defined in Section 4 of [RFC7234], excepting the requirement to calculate a secondary cache key):¶
This returns a list of lists of strings suitable for comparing to the parsed Variant-Keys (Section 3) that represent possible responses on the server that can be used to satisfy the request, in preference order, provided that their secondary cache key (after removing the headers covered by Variants) matches. Section 4.2 illustrates one way to do this.¶
This algorithm computes the cross-product of the elements of key-facets.¶
Given key-facets (a list of lists of strings), and key-stub (a list of strings representing a partial key), and possible-keys (a list of lists of strings):¶
This algorithm is an example of how an implementation can meet the requirement to apply the members of the Vary header field that are not covered by Variants.¶
Given incoming-request (a mapping of field-names to field-values, after being combined as allowed by Section 3.2.2 of [RFC7230]), and stored-response (a stored response):¶
This returns a Boolean that indicates whether stored-response can be used to satisfy the request.¶
Note that implementation of the Vary header field varies in practice, and the algorithm above illustrates only one way to apply it. It is equally viable to forward the request if there is a request header listed in Vary but not Variants.¶
For example, if the selected variants-header was:¶
Variants: Accept-Language=(en fr de), Accept-Encoding=(gzip br)
and the request contained the headers:¶
Accept-Language: fr;q=1.0, en;q=0.1 Accept-Encoding: gzip
Then the sorted-variants would be:¶
[ ["fr", "en"] // prefers French, will accept English ["gzip", "identity"] // prefers gzip encoding, will accept identity ]
Which means that the result of the Cache Behaviour algorithm would be:¶
[ ["fr", "gzip"], ["fr", "identity"], ["en", "gzip"], ["en", "identity"] ]
Representing a first preference of a French, gzip'd response. Thus, if a cache has a response with:¶
Variant-Key: (fr gzip)
it could be used to satisfy the first preference. If not, responses corresponding to the other keys could be returned, or the request could be forwarded towards the origin.¶
If the selected variants-header was:¶
Variants: Accept-Language=(en fr de)
And a request comes in with the following headers:¶
Accept-Language: de;q=1.0, es;q=0.8
Then sorted-variants in Cache Behaviour is:¶
[ ["de"] // prefers German; will not accept English ]
If the cache contains responses with the following Variant-Keys:¶
Variant-Key: (fr) Variant-Key: (en)
Then the cache needs to forward the request to the origin server, since Variants indicates that "de" is available, and that is acceptable to the client.¶
If the selected variants-header was:¶
Variants: Accept-Language=(en fr de)
And a request comes in with the following headers:¶
Accept-Language: es;q=1.0, ja;q=0.8
Then sorted-variants in Cache Behaviour are:¶
[ ["en"] ]
This allows the cache to return a "Variant-Key: en" response even though it's not in the set the client prefers.¶
Origin servers that wish to take advantage of Variants will need to generate both the Variants (Section 2) and Variant-Key (Section 3) header fields in all cacheable responses for a given resource. If either is omitted and the response is stored, it will have the effect of disabling caching for that resource until it is no longer stored (e.g., it expires, or is evicted).¶
Likewise, origin servers will need to assure that the members of both header field values are in the same order and have the same length, since discrepancies will cause caches to avoid using the responses they occur in.¶
The value of the Variants header should be relatively stable for a given resource over time; when it changes, it can have the effect of invalidating previously stored responses.¶
As per Section 2.1, the Vary header is required to be set appropriately when Variants is in use, so that caches that do not implement this specification still operate correctly.¶
Origin servers are advised to carefully consider which content negotiation mechanisms to enumerate in Variants; if a mechanism is not supported by a receiving cache, it will "downgrade" to Vary handling, which can negatively impact cache efficiency.¶
The operation of Variants is illustrated by the examples below.¶
Given a request/response pair:¶
GET /clancy HTTP/1.1 Host: www.example.com Accept-Language: en;q=1.0, fr;q=0.5
HTTP/1.1 200 OK Content-Type: image/gif Content-Language: en Cache-Control: max-age=3600 Variants: Accept-Language=(en de) Variant-Key: (en) Vary: Accept-Language Transfer-Encoding: chunked
Upon receipt of this response, the cache knows that two representations of this resource are available, one with a language of "en", and another whose language is "de".¶
Subsequent requests (while this response is fresh) will cause the cache to either reuse this response or forward the request, depending on what the selection algorithm determines.¶
So, if a request with "en" in Accept-Language is received and its q-value indicates that it is acceptable, the stored response is used. A request that indicates that "de" is acceptable will be forwarded to the origin, thereby populating the cache. A cache receiving a request that indicates both languages are acceptable will use the q-value to make a determination of what response to return.¶
A cache receiving a request that does not list either language as acceptable (or does not contain an Accept-Language at all) will return the "en" representation (possibly fetching it from the origin), since it is listed first in the Variants list.¶
Note that Accept-Language is listed in Vary, to assure backwards-compatibility with caches that do not support Variants.¶
A more complicated request/response pair:¶
GET /murray HTTP/1.1 Host: www.example.net Accept-Language: en;q=1.0, fr;q=0.5 Accept-Encoding: gzip, br
HTTP/1.1 200 OK Content-Type: image/gif Content-Language: en Content-Encoding: br Variants: Accept-Language=(en jp de) Variants: Accept-Encoding=(br gzip) Variant-Key: (en br) Vary: Accept-Language, Accept-Encoding Transfer-Encoding: chunked
Here, the cache knows that there are two axes that the response varies upon; language and encoding. Thus, there are a total of nine possible representations for the resource (including the identity encoding), and the cache needs to consider the selection algorithms for both axes.¶
Upon a subsequent request, if both selection algorithms return a stored representation, it can be served from cache; otherwise, the request will need to be forwarded to origin.¶
Now, consider the previous example, but where only one of the Vary'd axes (encoding) is listed in Variants:¶
GET /bar HTTP/1.1 Host: www.example.net Accept-Language: en;q=1.0, fr;q=0.5 Accept-Encoding: gzip, br
HTTP/1.1 200 OK Content-Type: image/gif Content-Language: en Content-Encoding: br Variants: Accept-Encoding=(br gzip) Variant-Key: (br) Vary: Accept-Language, Accept-Encoding Transfer-Encoding: chunked
Here, the cache will need to calculate a secondary cache key as per [RFC7234], Section 4.1 -- but considering only Accept-Language to be in its field-value -- and then continue processing Variants for the set of stored responses that the algorithm described there selects.¶
To be usable with Variants, proactive content negotiation mechanisms need to be specified to take advantage of it. Specifically, they:¶
Appendix A fulfils these requirements for some existing proactive content negotiation mechanisms in HTTP.¶
This section is to be removed before publishing as an RFC.¶
There is a prototype implementation of the algorithms in this document at <https://github.com/mnot/variants-toy>.¶
This specification registers the following entry in the Permanent Message Header Field Names registry established by [RFC3864]:¶
This specification registers the following entry in the Permanent Message Header Field Names registry established by [RFC3864]:¶
If the number or advertised characteristics of the representations available for a resource are considered sensitive, the Variants header by its nature will leak them.¶
Note that the Variants header is not a commitment to make representations of a certain nature available; the runtime behaviour of the server always overrides hints like Variants.¶
This appendix defines the required information to use existing proactive content negotiation mechanisms (as defined in [RFC7231], Section 5.3) with the Variants header field.¶
The syntax of an available-value for Accept is:¶
accept-available-value = type "/" subtype
To perform content negotiation for Accept given a request-value and available-values:¶
Note that this algorithm explicitly ignores extension parameters on media types (e.g., "charset").¶
This section defines variant handling for the Accept-Encoding request header (section 5.3.4 of [RFC7231]).¶
The syntax of an available-value for Accept-Encoding is:¶
accept-encoding-available-value = content-coding / "identity"
To perform content negotiation for Accept-Encoding given a request-value and available-values:¶
Note that the unencoded variant needs to have a Variant-Key header field with a value of "identity" (as defined in Section 5.3.4 of [RFC7231]).¶
This section defines variant handling for the Accept-Language request header (section 5.3.5 of [RFC7231]).¶
The syntax of an available-value for Accept-Language is:¶
accept-encoding-available-value = language-range
To perform content negotiation for Accept-Language given a request-value and available-values:¶
This protocol is conceptually similar to, but simpler than, Transparent Content Negotiation [RFC2295]. Thanks to its authors for their inspiration.¶
It is also a generalisation of a Fastly VCL feature designed by Rogier 'DocWilco' Mulhuijzen.¶
Thanks to Hooman Beheshti, Ilya Grigorik, Leif Hedstrom, and Jeffrey Yasskin for their review and input.¶