4.1. Calculating Secondary Keys with Vary
rfc 7234 4.1. Calculating Secondary Keys with Vary.md
节选解释
When a cache receives a request that can be satisfied by a stored
response that has a Vary header field (Section 7.1.4 of [RFC7231]),
it MUST NOT use that response unless all of the selecting header
fields nominated by the Vary header field match in both the original
request (i.e., that associated with the stored response), and the
presented request.
- 其中"satisfied"指的是URI/Method条件满足但是还未进行Vary匹配。
- MUST NOT感觉在强调特定的场景,这里不知道怎么表达(TODO)
- 注意"vary match"并没有和"When a cache receives a request that can be satisfied by a stored"场景绑定。
Vary
origin server发送(SHOULD)Vary字段的情况
An origin server SHOULD send a Vary header field when its algorithm
for selecting a representation varies based on aspects of the request
message other than the method and request target, unless the variance
cannot be crossed or the origin server has been deliberately
configured to prevent cache transparency. For example, there is no
need to send the Authorization field name in Vary because reuse
across users is constrained by the field definition (Section 4.2 of
[RFC7235]). Likewise, an origin server might use Cache-Control
directives (Section 5.2 of [RFC7234]) to supplant Vary if it
considers the variance less significant than the performance cost of
Vary's impact on caching.
当origin server的算法在决定representation时,如果使用了request message中除了method/request target之外的部分(显然,“之外的部分”范围是request message中的Header部分(当Vary取之为Header列表时)或者request message之外的部分(当Vary取值为*时))时, 可以发送Vary字段。
当如下情况时,不应该发送Vary:
- "cannot be crossed"
- "origin server has been deliberately configured to prevent cache transparency"
"cannot be crossed"
// gemini,场景我没有吃透
其核心逻辑都是:请求方和接收方处于两个完全不交织的通道中,缓存系统根本没有机会把 A 版本的响应发给 B 请求。
"origin server has been deliberately ..."
// gemini
透明缓存(Transparent Cache) 的理想状态是:无论客户端是从缓存获取数据,还是直接从源服务器获取数据,得到的内容应该完全一致。
为什么要阻止透明缓存?“阻止透明性”意味着源服务器故意让缓存和自己返回不一致的内容,或者故意让缓存失效。这通常出于以下几种商业或技术目的:
A. 灰度发布与 A/B 测试 服务器可能正在进行实验:
-
50% 的用户看到红色按钮,50% 看到蓝色按钮。
-
如果保持“透明性”,缓存可能会把红色按钮存下来发给所有人。
-
为了防止这种情况,服务器会刻意配置,不让缓存简单地存储和分发内容,从而打破这种一致性。
B. 插入动态广告或个性化内容 有些过时的架构中,中间代理服务器(如 ISP 的缓存)可能会修改响应内容(例如插入一段脚本)。如果源服务器允许这种行为,它实际上就是在放弃“透明性”。
发送Vary的意图
1. To inform cache recipients that they MUST NOT use this response
to satisfy a later request unless the later request has the same
values for the listed fields as the original request (Section 4.1
of [RFC7234]). In other words, Vary expands the cache key
required to match a new request to the stored cache entry.
通知cache,当Vary指定的所有的Header都match时,这个stored response才预备了resue的条件.否则就不能(MUST NOT)resue它。
2. To inform user agent recipients that this response is subject to
content negotiation (Section 5.3) and that a different
representation might be sent in a subsequent request if
additional parameters are provided in the listed header fields
(proactive negotiation).
通知user agent,response被内容协商约束,传递不同参数(因为节选中说“are provided in the listed header fields”,所以这里的参数取值为所有 和request相关包括message之外的部分)会导致获取到的representation不同。
match动作
- Vary中指定Header fields
- 对于某个满足1.条件的Header的2个值,如果他们满足特定条件(见下一节,语义相等),则认这个这个Header match
是否match的定义
The selecting header fields from two requests are defined to match if
and only if those in the first request can be transformed to those in
the second request by applying any of the following:
o adding or removing whitespace, where allowed in the header field's
syntax
o combining multiple header fields with the same field name (see
Section 3.2 of [RFC7230])
o normalizing both header field values in a way that is known to
have identical semantics, according to the header field's
specification (e.g., reordering field values when order is not
significant; case-normalization, where values are defined to be
case-insensitive)
当对比同一个Header的两个值时,如果这2个值的语义相等,则这个Header match(2个header-value对,header相同,value语义相同)。上述3种方式 可以帮助将2个字符串形式不同但语义相同的值转换成相同的字符串形式,方便程序进行match判断。
absent判定match
Vary: Header_a, Header_b
// request A
Header_b: ba
// request B
Header_b: ba
判定结果:
- Header_a match:Vary中指定了Header_a,但是A/B中都没有,这种情况也属于match
- Header_b match:显然,语义相同
Vary: * 判定match
A Vary field value of "*" signals that anything about the request
might play a role in selecting the response representation, possibly
including elements outside the message syntax (e.g., the client's
network address). A recipient will not be able to determine whether
this response is appropriate for a later request without forwarding
the request to the origin server. A proxy MUST NOT generate a Vary
field with a "*" value.
- Vary: *表示服务器在生成response representation时可能参考了所有关于request的内容,包括message之外的类容,例如:服务器基于请求的ip生成不同的类容。
- 所以recipient判定不了是否能复用带有"Vary: *"的response。
- proxy 禁止(MUST NOT)生成"Vary: *"
所以,如果是"Vary: *",Header的match总是fail
MUST NOT
"selected"操作
1.含义
The stored response with matching selecting header fields is known as
the selected response.
"The stored response with matching selecting header fields"指的是:Vary中指定的每一个Header fields在A/B中都match
//response(stored)
Vary: Header_a,Header_b...
// request A(stored)
Header_a: va
Header_b: vb
...
// request B
Header_a: va
Header_b: vb
...
另外,虽然4.1.小节中有上述表述,但是结合rfc 7234中多处上下文,"selected"的含义应理解为:
对于支持cacheable的方法:
- GET/HEAD
- 特定条件的POST
- 特定条件的PATCH
selected = URI Match + Method Match + Vary Match(如果存在)
2.适用范围
"selected"可以帮助你完成一些筛选,是一个独立的操作,切勿将其绑定特定场景;现在遇到的场景有:
- cache收到请求以后selected stored response
- cache企图通过HEAD请求更新GET方法时,假设HEAD为GET来进行"selected"操作
- 其他
"selected"的后续步骤
1.如果同时"selected"了多个
If multiple selected responses are available (potentially including
responses without a Vary header field), the cache will need to choose
one to use. When a selecting header field has a known mechanism for
doing so (e.g., qvalues on Accept and similar request header fields),
that mechanism MAY be used to select preferred responses; of the
remainder, the most recent response (as determined by the Date header
field) is used, as per Section 4.
可能出现"selected"了多个的情况,例如:
通常一个 URL 对应一个资源,但在 HTTP 中,由于 内容协商 (Content Negotiation) 的存在,同一个 URL 可能会有:
- 不同语言版本(中文 vs. 英文)
- 不同格式版本(WebP 图片 vs. JPEG 图片)
- 不同压缩方式(Gzip vs. Brotli)
服务器通过 Vary 响应头告诉缓存:这个资源是根据客户端请求头里的某个字段(比如 Accept-Language)来变化的。于是缓存里就会存下好几份“长得不一样”的备份。
如果"selected"了多个,可以按照如下规则挑选:
- 使用"known mechanism"进行判断,如果不行则
- 使用最近收到的response
2.一个"selected"的都没有
If no selected response is available, the cache cannot satisfy the
presented request. Typically, it is forwarded to the origin server
in a (possibly conditional; see Section 4.3) request.
需要注意的是,这种情况rfc只是说可以将请求转发到origin server,并没有说必须这样做。
Vary使用实际场景
1. 响应式内容适配(User-Agent)
这是最经典的用法。假设你的网站对同一个 URL,根据用户是手机还是电脑访问,返回完全不同的 HTML 代码。
- 场景描述: 服务器通过 User-Agent 判断设备类型。
- 配置: Vary: User-Agent
- 作用: 如果没有这个头,第一个用 iPhone 访问的用户会让 CDN 缓存移动端页面,随后用电脑访问的用户可能会看到错位的移动端布局。有了它,缓存服务器会根据 User-Agent 为不同设备存储不同的副本。
2. 多语言版本切换(Accept-Language)
如果你的应用支持国际化,且没有通过 URL(如 /en/ 或 /zh/)来区分语言,而是根据浏览器的语言设置自动分发。
- 场景描述: 服务器读取 Accept-Language 头来决定返回中文还是英文页面。
- 配置: Vary: Accept-Language
- 作用: 确保中文用户缓存的是中文版,英文用户缓存的是英文版,防止“跨语种”的缓存污染。
3.内容压缩方案(Accept-Encoding)
为了节省流量,服务器通常会对静态资源进行压缩。
-
场景描述: 现代浏览器支持 br (Brotli) 或 gzip,但极少数老旧客户端可能不支持压缩。
-
配置: Vary: Accept-Encoding
-
作用: 缓存服务器会根据客户端的支持情况分发资源。
- 副本 A:经过 Gzip 压缩的代码。
- 副本 B:未经压缩的原始代码。
- 注意: 这是目前互联网上最常见的 Vary 用法,几乎所有主流 CDN 默认都会处理这个字段。