本文要点
先给有条件的答案:如果你对同一个URL返回不同语言的内容,就应该在Vary里写上Accept-Language
如果你基于用户代理提供内容优化,比如响应式设计,可能会想在Vary里加上User-Agent
Vary可以同时列多个请求头

本文目录
先看清:缓存默认只按URL区分,Vary改变的是这个键
如果你在响应头里看到Vary,先要弄清它改的是什么。按MDN的HTTP缓存说明,缓存区分响应的方式本质上基于URL:同一个URL对应一份响应。但响应内容并不总是相同,即使URL一样,在执行内容协商时,来自服务器的响应可能取决于Accept、Accept-Language和Accept-Encoding请求标头的值。
Vary就是用来处理这种情况的。在Vary的值里写上某个请求头名称,缓存就会基于响应URL和该请求头的组合进行键控,而不是仅仅基于响应URL。
一个自建情境(示例,不是对某个真实站点的记录):你在练习目录里维护一个自建页面,服务器对同一个地址返回两种语言版本。下面所有请求头值和响应内容都是练习用的自建数据。
适用范围:本文只讲Vary如何让缓存基于URL之外的请求头组合来区分响应,以及把User-Agent放进Vary为什么降低缓存复用。不讲缓存过期时间,不讲Cookie作用域。
Accept-Language放进Vary:中英文版本各存一份
先给有条件的答案:如果你对同一个URL返回不同语言的内容,就应该在Vary里写上Accept-Language。
按MDN的说明,对于带有Accept-Language: en标头并已缓存的英语内容,不希望再对具有Accept-Language: ja请求标头的请求重用该缓存响应。在这种情况下,可以通过在Vary标头的值中添加Accept-Language,根据语言单独缓存响应。
写法是:
Vary: Accept-Language这会导致缓存基于响应URL和Accept-Language请求标头的组合进行键控。MDN给出的对照表可以这样理解:
- 地址/index.html + Accept-Language: ja-JP → 一份响应
- 地址/index.html + Accept-Language: en-US → 另一份响应
- 地址/style.css + Accept-Language: ja-JP → 一份响应
- 地址/script.js + Accept-Language: ja-JP → 一份响应
关键点在于:同一个URL下,不同语言值对应不同的缓存条目,彼此不会串用。日文请求不会拿到已缓存的英文响应,反过来也一样。
这里要分清Vary管的是“按什么键区分”,不是“存多久”。它不改变响应能存多长时间,只改变缓存拿哪一份来复用。
一个自建示例(练习数据,不是真实站点记录):假设你的自建页面在服务器端根据Accept-Language返回中文或英文正文,响应头写成Vary: Accept-Language。第一次用中文请求,缓存存下一份;第二次用英文请求同一个地址,缓存不会把中文那份拿出来复用,而是按英文这个键另存一份。
User-Agent放进Vary:变体太多,缓存很难被复用
如果你基于用户代理提供内容优化,比如响应式设计,可能会想在Vary里加上User-Agent。按MDN的说明,这样做的问题是:User-Agent请求标头通常具有非常多的变体,这大大降低了缓存被重用的机会。
这句话可以拆成两层理解。第一层,User-Agent的值不是少数几个固定选项,而是数量庞大、彼此不同的字符串。第二层,Vary是按请求头的值来分键的,值有多少种,理论上就可能产生多少个缓存条目。
结果就是:缓存里堆着大量只对一个User-Agent值有效的条目,下一个请求带着不同的User-Agent进来,命中的概率很低,只能回源。缓存本来要减少回源,这样一来复用机会被大幅压缩。
MDN给的方向是:如果可能,请考虑一种基于特性检测而不是基于User-Agent请求标头来改变行为的方法。也就是说,把“判断客户端是什么”这件事从请求头挪到别处,而不是让缓存按User-Agent分键。
一个自建示例(练习数据,不是真实站点记录):假设你在自建页面里写了Vary: User-Agent,然后分别用几个不同的User-Agent字符串请求同一地址。每次值不同,缓存就要另存一份;下次再来一个没见过的值,仍然要回源。这个示例只用来观察“值多则条目多”这一层,不涉及任何真实浏览器统计。
适用范围:本节只讲User-Agent变体多导致缓存复用机会下降这一条,不涉及具体浏览器占比数据,也不给性能数值。
多个请求头一起写进Vary:组合键怎么理解
Vary可以同时列多个请求头。写法是把它们用逗号隔开,例如:
Vary: Accept-Language, Accept-Encoding这时缓存按URL加上这两个请求头的组合来分键。判断顺序可以这样定:先看URL是否相同,再看这两个请求头的值是否都相同。任意一项不同,就不是同一个缓存条目。
这带来一个直接后果:列进Vary的请求头越多,可能的组合数就越多,缓存条目被切得越细,单个条目被复用的机会就越小。所以Vary里该写哪些头,判断标准是“响应内容是否真的随这个头变化”。只有内容确实随它变,才需要把它写进去。
一个自建示例(练习数据,不是真实站点记录):假设你的自建页面同时按语言和编码返回不同内容,响应头写成Vary: Accept-Language, Accept-Encoding。那么“中文+某编码”和“英文+同一编码”是两个条目,“中文+另一编码”又是第三个条目。
需要说明的是:MDN资料里明确举例的是Accept、Accept-Language和Accept-Encoding这几个内容协商相关的请求头,以及User-Agent。本文不替其他请求头补写“应该放进Vary”的结论。
检查结果:在开发者工具里核对Vary有没有按预期分键
写完Vary之后,怎么确认它起作用了?可以按下面几步在浏览器开发者工具里核对(以下为编辑整理的观察方法,不是对某个真实站点的测试记录):
1. 打开Network面板,请求你的自建练习地址,点开这条响应,看响应头里有没有Vary这一行,值是不是你写的那个请求头名称。
2. 用同一个地址再发一次请求,但换一个Accept-Language值。观察这次请求是命中了缓存,还是回源拿到了另一份内容。
3. 对照两次响应,确认不同语言值拿到的是不同内容,而不是把上一份直接复用。
一个自建示例(练习数据,不是真实站点记录):你在练习目录里维护一个自建页面,第一次请求带Accept-Language: zh-CN,第二次带Accept-Language: en-US。如果Vary写对了,两次拿到的正文应该分别对应中文和英文;如果Vary漏写,第二次可能直接复用第一次的中文响应。
排查方向:如果换了语言值却拿到旧内容,先检查Vary这一行是不是真的出现在响应头里,以及请求头名称有没有拼错。Vary里写的是请求头的名称,名称对不上,缓存就不会按它分键。
使用限制:Vary管的是分键,不替代private,也不解决过期
最后划清Vary的边界,避免把它当成万能开关。
第一,Vary不负责“这份响应能不能存”。它只改变缓存按什么键区分响应。响应能不能存、存多久,由Cache-Control等其他指令决定,本文不展开这部分。
第二,个性化内容不要靠Vary来隔离。按MDN的说明,对于使用cookie来防止其他人重复使用缓存的个性化内容的应用程序,应该指定Cache-Control: private,而不是为Vary指定cookie。也就是说,Vary里写cookie不是处理个性化内容的正确做法。
第三,Vary里列的头越多,缓存条目越细,复用机会越小。这一点在User-Agent上尤其明显,因为它的值变体非常多。
第四,本文所有示例都是自建的练习数据,放在练习目录里操作,不涉及任何真实站点、真实用户数据或真实浏览器统计。资料没有给出的数值、占比和性能结论,本文不补。
观看知识要点视频 18 秒
18秒无旁白要点导览,文字来自已审核正文;完整步骤、条件及参考资料见下方。
参考资料
以下原始资料提供本文依据;请结合日期、适用条件和产品说明查阅。
请结合原始资料的适用条件阅读。
返回浏览器工具 →