30 分钟 HTTP 查漏补缺之 Vary

写在前面

最近抽空参加了几场大厂的面试,忽然发现一个现象,就是不论面试偏服务端的职位仍是偏客户端的职位,不论面试的 5 年以上的高级职位,仍是 3 年左右的中级职位,面试官开头所问问题必然是关于 HTTP 的。web

我记得以前找工做的时候,彷佛都是先考察一些职位所需技能领域的基础知识,以后再考察关于 HTTP 的东西,如今你们都将 HTTP 的问题放到面试的开头来问,我觉的应该是愈来愈多的招聘者意识到,做为一个 Web 开发者,HTTP 真的是过重要了,必需要先考察。面试

回想起来,这几年我本身对于 HTTP 的学习大可能是碎片化的,不少东西没法系统地在脑海中组织起来。虽然感受 HTTP 总体的学习难度是比较低的,可是各个知识点交杂在一块儿又变得很复杂很难,相信你们都会有同感。同时有些知识点,若是在实际工做中没有采坑或者刻意深挖的话,很天然地就被忽略了。chrome

因为在以前一次面试中,被狠狠地问了若干关于 Vary 的问题,因此想抽一些时间整理一下那些比较容易让人忽略的知识点,算是查漏补缺吧。json

内容协商

首先须要了解的是内容协商这个术语。当咱们经过某个 URI 来访问其指向的资源时,HTTP 协议能够经过内容协商机制提供资源的不一样的展现形式。跨域

若是缺乏服务端开发经验话,对于这个概念可能会感到陌生,但其实咱们在工做中几乎都会遇到它,好比在调用接口时,常常会用到 Accept: application/json 这个头部,有时可能会用到 Accept: application/xml,这就是内容协商,前者指望接口返回 json 格式的数据,然后者指望返回 xml 格式的数据。浏览器

通常客户端涉及的常见头部有如下几个:缓存

  • Accept: 声明客户端能够处理的资源格式
  • Accept-Charset: 声明客户端能够处理的字符集类型
  • Accept-Language: 声明客户端能够理解的天然语言
  • Accept-Encoding: 声明客户端支持的编码格式

而服务端涉及的常见头部包括:服务器

  • Content-Type: 指示资源的 MIME 类型
  • Content-Language: 指示该资源所指望的天然语言
  • Content-Encoding: 指示资源使用该编码格式进行内容转换

仔细观察的话,会发现它们其实存在着必定程度的对应关系。缘由也很简单,既然是协商,那必然就会和两我的在进行说话同样,若是二者之间的对话内容没有关联,他们还怎么沟通呢?客户端和服务端进行沟通同理。app

若是想详细了解该机制,能够参考MDN的文档,很详细,这里就很少说了。ide

这里顺带说明一下,对于内容协商机制中涉及的头部,从 web 发展历史上来看已经没有什么实质的用途了,缘由以下(有兴趣的话能够阅读这篇wiki):

  • Accept-Charset: 因为 utf-8 成为主流的字符集类型,因此使用其余字符集类型的服务能够将其转换为 utf-8 类型
  • Accept-Language: 大致包含如下几点

    • 提供多种语言服务的网站每每是基于某种特定语言构建,再提供其余语言支持的,这样每种语言类型的内容在质量上层次不齐,而访问者可能会更倾向于内容质量更高的那一种语言,而内容协商机制没法替代用户的主观判断
    • 实践中,对于切换网站语言的功能,切换方式每每更倾向于主动切换(好比提供一个切换的按钮)而非自动切换
    • 浏览器在用户不提供语言相关配置的状况下,很难猜想用户的天然语言倾向(通常可能会根据地理定位、ip等因素猜想),打个比方,好比我会常常出差去日本,但这不表明我会说日语,同时虽然我挂了加拿大的 vps,可是提供中文内容的网站,我仍是倾向于看中文
  • Accept: 与 Accept-Language 相似,一样由于内容的格式会因用户的主观意识而不一样,还有诸多其余因素制约内容协商机制,因此最终失败了。

惟一有些用途的是 Accept-Encoding,但鉴于现在大部分现代浏览器都已支持多种压缩方式(常见的如 gzip、br),所以必定程度上已经不须要额外声明这个头部了,虽然大部分浏览器都会自动发送这个头部,但其实这会形成额外 23 字节的浪费。

Vary 头部

在理解(或者巩固)了内容协商的概念后,就能够介绍 Vary 这个头部了。直接引用 MDN 对于它的描述:

The Vary HTTP response header determines how to match future request headers to decide whether a cached response can be used rather than requesting a fresh one from the origin server.

Vary 是一个HTTP响应头部信息,它决定了对于将来的一个请求头,应该使用一个缓存做为响应仍是向源服务器请求一个新的响应。

单纯靠文档对于 Vary 的描述来理解它实际上是有些困难的,最起码我会有这种感受。

这个头部的语法和其余的 HTTP 头部相似,以下:

Vary: <header-name>, <header-name>, ...

不一样的头部之间使用逗号进行分割,同时能够指定 * 为它的值,这样等价于将资源视为惟一,并不进行缓存,但这并非最佳实践,所以不建议这么作。

Vary 的工做原理

一句话归纳它的工做原理就是,就是它表示某个响应因某个响应头部而不一样。举个例子,好比 Vary: Accept 的意思即为,响应因请求资源格式头部而不一样,那么经过相同 URI 访问的资源就能够根据这个头上知道其内容格式不一样。

但咱们已经知道,对于大部份内容协商机制中涉及的头部,已经被看做是失败的,那么 Vary 和这些头部搭配使用还有什么意义呢?话虽如此,但 Vary 还能够与 HTTP 中其余的头部来搭配使用,从而知足不少应用场景下的特殊需求,好比动态服务、防止缓存错乱等。

Vary 的应用场景

如下简单罗列一些经常使用的应用场景以及采坑指南。

Vary 与 动态服务

关于动态服务,最多见的莫过于 Vary: User-Agent。众所周知,UA 是一段特征字符串,一般包含区分客户端类型、操做系统、版本号等信息,随着移动 web 应用变得越流行,一个应用网站同时提供桌面和移动两种版本的应用是很常见的事情。经过设置 Vary: User-Agent 头部,对于搜索引擎,对于关键字的搜索结果能够提供更加准确的应用版本,对于客户端,可使其从缓存服务器获取到相应应用类型的缓存版本,而不是错误地将桌面版缓存传递给移动版应用。

web 应用的性能在加载速度这一指标上,很大程度上取决于加载资源的大小,而图片资源是所占比例最大的一块。为了减小图片的大小,除了对常见的图片格式进行压缩之外,chrome 推出的 WebP 格式也是不错的选择。可是这里的问题是,不是全部的浏览器都支持 WebP 图片格式的,因此这里使用 Vary: Accept 来针对浏览器的支持状况返回相应的缓存副本,支持则返回 WebP 格式,不支持则返回缩略图或者原图。

还有其余关于动态服务的场景,好比要针对不一样分辨率的屏幕加载不一样质量的图片(Client Hints 相关的头部)、针对不一样用户身份提供不一样的资源(Cookie头部)等等。

Vary 与 缓存错乱

有时候咱们会发现响应中存在 Vary: Accept-Encoding 头部信息,我原先按照内容协商机制中所描述的内容来理解,但到后来才发现,其实很大程度上是为了防止缓存错乱的问题。

设想一下,若是没有这个头部,当两个分别支持 gzip 和 不支持 gzip 的客户端对同一份资源进行获取时,结果会变得十分微妙。若是不支持 gzip 的客户端先访问,缓存代理会缓存未压缩的版本,那么当支持 gzip 的客户端再访问时,因为命中缓存,虽然它支持 gzip 但也只能加载未压缩的资源。反过来一样如此,支持 gzip 客户端先访问,则缓存代理会缓存压缩版本,当不支持 gzip 的客户端再访问时,缓存一样命中,可是因为它没法对压缩资源解码,因此会呈现乱码。

经过 Vary: Accept-Encoding 咱们能够防止这种状况的发生,由于 Vary 在这里实际上是扮演着校验器的角色,它会进一步对命中缓存的资源进行再校验,若是发现头部信息不一样,则会将缓存资源视为无效,从而将请求继续转发至源服务器。这对于缓存代理服务器也有必定的益处,由于能够有有依据地针对不一样的 Accept-Encoding 缓存不一样的资源副本。

Vary 与 缓存命中率

Vary 虽然能够防止缓存错乱,但并不表明能够滥用,盲目的使用会拔苗助长,好比以前说起的 Vary: *,这样等价于将每一个请求视为惟一,而且不缓存其响应资源,除非有意为之,否则没有人会牺牲缓存带来的性能提高。

同时对于一些 Header 的值是开放性的,好比以前说起的 User-Agent,若是单纯从字面量来匹配的话,众多桌面浏览器的值会因各类因素而不一样的,若是仅是简单地将 UA 做为区分桌面端和移动端的依据,那么缓存命中率会达到一个很低的水平。如何解决这个问题呢?能够将这些 UA 头部的值进行标准化,好比能够经过正则匹配全部桌面浏览器的 UA 并从新更改成 Desktop,以后再转发至缓存代理和源服务器,这样有利于提升缓存命中率,关于这部分的内容,能够参考这篇文章,其中有很细致的讲解。

因此咱们要时刻留意,在使用 Vary 时,必定要根据缓存命中率做出调整,在不发生缓存错乱的状况之下,尽量的提升资源的缓存命中率。

Vary 与 CORS

对于跨域的有状况,Vary 也包含一些内容。HTTP 协议规定,当服务端响应包含 Access-Control-Allow-Origin 头部,且它的值是一个具体的域名而不是通配符 *,那么这时必需要包含 Vary: Origin 这个头部。

为何要包含这个头部,由于请求头中的 Origin 头部表明了该请求来源的具体域名信息,那么对于不一样域名网站所发起的请求,会使用仅属于它自己的缓存。通常而言,咱们不多会遇到这种问题,由于通常都将 Access-Control-Allow-Origin 设置为了 *,至少我本身是这样的。若是想进一步了解 Vary 和 CORS 的内容,能够参考这篇文章

最后

差很少就这么多内容了,若有错误,还望指正。

参考连接

相关文章
相关标签/搜索