我们刚刚上线了 HTTP 中最丑部分的支持:Vary 头

10

响应首部,Vary,被称为”HTTP中最丑陋的部分,我们尚未改进。同一篇帖子形容它是一个“糟糕、笨拙的机制”,并且在中介之间的互操作性“相当糟糕”。

这通常是理智的工程师举手慢慢退缩的地方。

这并不完全是对Vary但丑并不意味着没用。

一个URL可以有多个正确的响应。例如,服务器可能会向不同的浏览器发送不同的图像格式。如果缓存忽略Vary它有可能向请求提供错误的字节。

但如果它把每个原始头值都当作独立的值处理,少数类似请求可能会扩散成数千个几乎无法重用的缓存条目。Vary告诉缓存哪些请求字段可能影响响应,但不告诉缓存哪些字段不同真正重要。

Vary现在支持已提供于缓存规则在每个计划中都是如此。源端仍然会指定可能影响响应的请求头,但你可以决定 Cloudflare 如何处理每个请求。

你可以规范已知的协商头,当这些细微差别重要时传递精确值,或者在变化过于不可预测时绕过缓存。源方声明可能的变化,你决定缓存实际有多少变化。

Vary 的工作原理

Vary是标准HTTP 响应头告诉中介缓存(如 Cloudflare),哪些请求字段可能影响源方发送的响应。站点使用Vary同一URL提供不同语言、图像格式、压缩方案或区域内容。

取一个URL,产生两个有效的表示。浏览器请求一个网页:

原地址返回 HTML,并识别Accept作为可能影响回答的领域:

API客户端可以请求相同的URL,但偏好不同:

这次,正确的响应是 JSON。该Vary: Accept头部告诉缓存,仅靠 URL 不足以在响应之间做出选择。请求的Accept价值也必须被考虑。

没有Vary,先进入缓存的响应可以同时发送给两个客户端。如果HTML获胜,API客户端将获得标记,其JSON解析器失败。如果JSON获胜,浏览器预期网页时会收到API响应。

Vary它防止缓存向请求客户端发送错误的响应。但这也带来了一个更难的问题:当两个请求包含不同的头值时,它们真的存在需求不同的反应?

当正确的缓存变得毫无意义时

Vary 可以告诉缓存哪些请求字段可能影响响应。它不会告诉缓存响应代表什么。例如,假设一个只提供英语、法语和德语内容的起源。客户端可能会发送:

而其他客户可能会要求:

这里两个请求都更倾向于使用英文。原点的响应可能会将两个请求映射到完全相同的英文响应。但缓存对原始值进行比较不能安全假设它们是等价的。

它们的顺序不同,且语言标签(即原点不区分)。因此缓存可能会将它们存储为不同的变体,即使它们的响应体包含相同的字节。

这是Vary的核心问题。应用程序通常从庞大的请求值集合中生成一小部分有限的表示。起源理解成千上万种语言偏好会合并成三种支持语言,而缓存通常不会。

当响应在多个字段上变化时,这个问题会加剧。一个字段中有十个可能的值会产生十个变体。三个字段中的十个值可以产生1000个组合。

真实的标头可以有更高的基数:User-Agent数值众多,Cookie 可以独一无二地分配给单个访客,偏好页首的排序、格式(空格和制表表很重要!)也可能不同,质量价值.

结果是缓存可以完全正确且几乎永久冷(条目永远不会被重复使用)。相同的响应可以分散在接收流量过少无法保持热缓存的条目中。它们可能占用容量,驱逐彼此之间,降低缓存命中率,并向起始服务器发送更多请求。

驱逐可以移除冷录,但不能仅仅因为响应相同就合并它们。

分析在近5万个热门网站的1.2亿多条回复中,发现近3000个网站在四个或更多字段上有所差异。有些字段在10个、23个甚至47个字段上有所差异。

我们希望确保客户拥有使用所需的工具Vary适当时,但又不能让它们变成无用的缓存。

某些高基数变化是有意为之。CDN或反向代理可以注入诸如地理区域等值,以实现内容可预测的划分。当可能的值被控制且每个组件对其含义达成一致时,这种方法有效。

没有这些约束,缓存会分裂成可能永远无法重复使用的变体。

这就是我们需要解决的设计问题来支持它Vary.我们需要保留足够的变异以提供正确的响应,同时不允许请求间的偶然差异破坏缓存效率。

缓存规则控制的不同

Cloudflare 客户已经有多种方式处理协商内容,类似于Vary.他们可以绕过缓存,让原节点处理,在自定义缓存密钥或其他规则,使用工蜂,或使用以下特征变换图像.

这些选项依然有用,但它们要么放弃缓存,要么重复应用逻辑,需要编写额外代码,要么满足更狭窄的使用场景。Vary in Cache Rules通过将支持拆分为两个决策,可能填补这些现有功能之间的空白:

  1. 起源用途Vary以识别可能影响响应的请求头。
  2. 缓存规则决定了Cloudflare如何处理每个头部的值。

A缓存规则不强制每个响应vary.如果原点没有返回VaryCloudflare 通常缓存响应,尽管规则仍可能重写Accept以及Accept-Language然后再转发请求给起始机构。

当源返回 Vary 时,Cloudflare 会为每个命名的头部使用配置好的动作。没有单独设置的头部使用规则的默认动作.三种可用的行动是:

我们推荐正常化作为默认值。对于具有个人值或无界值的单个头部,请使用绕行.用途直通当精确值改变响应时。

例如,直通即使原点视为等价值,也能保持大小写、空白、顺序和重复值的区别。其中Vary: X-View以及直通,这三个值分别生成独立的缓存键:

X-View: compact,full

X-View: Compact,full

X-View: compact, full

足够的偶发变化可以将可重复使用的响应变成缓存中多个一次性变体。

无论配置了什么操作,Vary: *总是绕过缓存。这意味着请求的任何方面,甚至是HTTP消息之外的信息(如客户端的IP地址),都可能影响源方选择的响应。

因此,Cloudflare无法在不联系源方的情况下,将该响应用于后续请求。

响应如何通过缓存

让我们跟随其中一个/catalog通过Cloudflare收到上层请求。

在第一个请求中,Cloudflare 没有存储Vary数据,因此缓存查询会漏掉。匹配的缓存规则可以在Cloudflare联系起点之前规范配置字段。

这可能在Cloudflare还不知道最终响应是否包含之前发生Vary.缓存规则定义了允许的归一化;响应随后决定这些字段是否成为缓存变体的一部分。

这种排序很重要。如果 Cloudflare 将多个原始值归入一个归一缓存键,但原点仍接收这些原始值,原点可能产生不同的响应,缓存后来会视为可互换。

转发归一化值可以保持起点选择与缓存匹配对齐。

原点的回答是:

Vary: Accept, Accept-Language

Cloudflare 记录这些头部名称,并将响应存储为缓存变体。根据缓存规则处理的头部值,将该变体与同一资源的其他变体区分开来。

当另一个请求/catalog到达时,Cloudflare 从资源开始基础缓存密钥通常包括URL加上其他配置的键字段。然后读取存储的Vary在新请求中对这些头部应用缓存规则,以识别匹配的缓存变体。

假设它们归一化为:

Accept: text/html

Accept-Language: en,fr

Cloudflare 利用这些值直接查找匹配的缓存变体。它不会逐一比较请求与每个存储的变体。

如果存在匹配的变体且是新鲜的,则该请求为缓存命中。如果没有,Cloudflare 会将请求发送到源端,并可能将结果响应作为另一个变体存储。

源响应闭合了循环。对于每个命名为 的头部Vary,Cloudflare 使用该头部配置的动作,或者如果头部未单独列出,则使用规则的默认动作:

  • 如果它不包含VaryCloudflare 会正常缓存它。
  • 如果每个命名的头都解析为规范化或直通,Cloudflare 可以将响应存储为缓存变体。
  • 如果任何有命名的字段绕行Cloudflare 不存储响应。
  • 如果响应包含Vary: *Cloudflare 不存储这些数据。

这对起始点负有重要责任。每个可缓存的响应如果根据请求字段不同,都必须返回相应的Vary头部一致性,包括错误和后备响应。如果某个响应遗漏了该响应,Cloudflare 可以缓存该响应,而无需保持隔离所需的方差。

图中的缓存键是概念性的。后者的请求假设响应是新的缓存。

任何针对缓存资源的清除都涵盖其所有变体。现有资源要求清理缓存时,自定义缓存密钥依然适用。

更改Vary配置不会自动清除现有内容。新策略可能会产生不同的缓存密钥:请求可能会错过并在新密钥下重新填充,而旧条目则保留直到过期或被清除。

归一化将等效请求保持在一起

还记得上面要求英语和法语的请求吗?

Accept-Language: en-US, fr;q=0.8

Accept-Language: fr;q=0.8, en-GB

两个请求都更倾向于用英语,但直通将它们视为不同的变体。如果缓存规则允许en,fr, 和de,正常化将两者都归约为en,fr允许它们共享缓存的响应。

为此,Cloudflare 在Accept,Accept-Language, 和Accept-Encoding然后按 排序质量价值,最高优先,按字母顺序排列以打破平局。因此客户端的排序不会影响缓存键。

排序后,Cloudflare 会剔除具有非零质量值的条目参数。它也可能丢失q=0(“不可接受”),当缩短语言标签或过滤到配置的格式和语言时。

例如,en-US;q=0可以变成 en。使用直通Accept或Accept-Language如果原公司需要看到这些排除条款。

你也可以配置规则,只保留指定的媒体类型或语言Accept以及Accept-Language.地区语言标签例如:en-US除非完整标签配置好,否则简化为他们的基础语言 en。

这样你就可以对齐归一化根据你的起源所适用的格式和语言。

为了保持原点选择与缓存匹配的一致性,Cloudflare 会转发归一化后的Accept以及Accept-Language值传到原点。它也会转发归一化 Accept-Encoding当值Respect Strong ETags启用。

其他头部仅用于缓存匹配而规范化。

在缓存规则中配置 Vary

在Cloudflare仪表盘中,前往缓存>缓存规则创建或编辑规则,使响应符合缓存资格,并添加瓦里设置。设置默认行为,然后添加你预期的起源名称的头部。

同样的配置也可以通过规则集API在http_request_cache_settingsphase。默认设置会为你的起始名称的头选择一个备用操作Vary你没有单独配置。

此例对归一化Accept以及Accept-Language映射到一组配置好的格式和语言。默认的规范化操作也适用于其他命名的头部Vary:

这是一个完整的请求机构PUT前往http_request_cache_settings阶段入口点。APUT替换该入口点中的所有规则。如果你已经有缓存规则,请将它们包含在规则数组中,或者使用相应的单规则创建或更新操作。

如果源节点为每种媒体类型和语言对提供一种表示方式,则有六种内容组合。这并不能限制缓存的键数为六个。偏好排序、缺失的头部以及归一化为空的值可能会产生更多密钥。

保持支持的集合较小,并明确定义规则的边界。部署后,测试同一 URL 但头部值不同,这些头值应归一化为相同的缓存变体。发送来自同一客户端的测试请求,确认返回预期格式和语言,并检查CF-缓存状态.缓存填充后寻找命中,并调查持续未命中或意外绕过响应。

关于限制、更多示例以及如何在 Terraform 中设置,请参见变异文档.

为什么不使用自定义缓存密钥?

此时,一个显而易见的问题是,“为什么不增加Accept以及Accept-Language转给自定义缓存密钥?”

当这些字段始终是资源身份的一部分时,这种方法是有效的。但自定义缓存键会为规则覆盖的每个响应添加配置的维度,无论原节点是否使用这些。

Vary是响应驱动的,但在同一基键下可缓存的响应需要一致的Vary田野。

当请求属性始终定义资源时,使用自定义缓存键。使用Vary当源方在可缓存响应中声明相同的请求字段集时。除非重复是有意并经过测试,否则避免在两者中放置相同的头部。

今天就使用Vary in Cache Rules吧!

Vary这有助于解决一个显而易见的问题:一个URL可以有多个正确响应。但它给缓存带来了更难的问题:哪些请求差异真正重要?起始节点知道它能提供哪些响应。

缓存需要知道哪些请求可以重复使用每个响应。

Vary缓存规则连接这两个视图。起点标识可能影响响应的请求字段。你决定是否正常化值,使用直通以获取精确差异,或者将响应不放入缓存。

Vary从未丑到无法用。但手动配置支持的格式和语言可能并不适合所有应用。我们正在评估以下的想法是否已过期的可用性提示草图可以通过让起源直接描述它们所服务的表征来减少这项工作。

Vary在缓存规则现已通过Cloudflare 仪表盘,规则集 API, 和地球化.

参与讨论