30 分钟 HTTP 查漏补缺之 Vary

2018-10-11 admin

写在前面

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

我记得之前找工作的时候,似乎都是先考察一些职位所需技能领域的基础知识,之后再考察关于 HTTP 的东西,现在大家都将 HTTP 的问题放到面试的开头来问,我觉的应该是越来越多的招聘者意识到,作为一个 Web 开发者,HTTP 真的是太重要了,必须要先考察。

回想起来,这几年我自己对于 HTTP 的学习大多是碎片化的,很多东西无法系统地在脑海中组织起来。虽然感觉 HTTP 整体的学习难度是比较低的,但是各个知识点交杂在一起又变得很复杂很难,相信大家都会有同感。同时有些知识点,如果在实际工作中没有采坑或者刻意深挖的话,很自然地就被忽略了。

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

内容协商

首先需要了解的是内容协商这个术语。当我们通过某个 URI 来访问其指向的资源时,HTTP 协议可以通过内容协商机制提供资源的不同的展示形式。

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

一般客户端涉及的常见头部有以下几个:

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

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

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

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

如果想详细了解该机制,可以参考MDN的<a>文档</a>,很详细,这里就不多说了。

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

  • 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,之后再转发至缓存代理和源服务器,这样有利于提高缓存命中率,关于这部分的内容,可以参考这篇<a>文章</a>,其中有很细致的讲解。

所以我们要时刻留意,在使用 Vary 时,一定要根据缓存命中率作出调整,在不发生缓存错乱的情况之下,尽可能的提高资源的缓存命中率。

Vary 与 CORS

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

为什么要包含这个头部,因为请求头中的 Origin 头部代表了该请求来源的具体域名信息,那么对于不同域名网站所发起的请求,会使用仅属于它本身的缓存。一般而言,我们很少会遇到这种问题,因为一般都将 Access-Control-Allow-Origin 设置为了 *,至少我自己是这样的。如果想进一步了解 Vary 和 CORS 的内容,可以参考这篇<a>文章</a>。

最后

差不多就这么多内容了,如有错误,还望指正。

参考链接

  • <a>内容协商</a>
  • <a>Best Practices for Using the Vary Header</a>
  • <a>IE 与 Vary</a>
  • <a>CORS</a>
  • <a>Vary</a>
  • <a>Response with Vary Header</a>
  • <a>Understanding Vary Header</a>
  • <a>Getting the most out of Vary with Fastly</a>
  • <a>Why not conneg</a>
  • <a>条件型 CORS 响应下因缺失 Vary: Origin 导致的缓存错乱问题</a>

原文链接:https://segmentfault.com/a/1190000016648967

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-42415.html

文章标题:30 分钟 HTTP 查漏补缺之 Vary

相关文章
ajax教程之ajax使用Http请求
ajax中是如何让使用http请求的呢? 在传统的JS编程中,如果您希望从服务器上的文件或数据库中得到任何的信息,或者向服务器发送信息的话,就必须利用一个 HTML 表单向服务器 GET 或 POST 数据。而用户则需要单击“提交”按钮来发...
2015-11-12
DOM之通俗易懂讲解
DOM 是所有前端开发每天打交道的东西,但是随着 jQuery 等库的出现,大大简化了 DOM 操作,导致大家慢慢的 “遗忘” 了它的本来面貌。不过,要想深入学习前端知识,对 DOM 的了解是不可或缺的,所以本文力图系统的讲解下 DOM 的...
2016-01-13
JS教程之基础
javascript教程之什么是 JavaScript? JavaScript 被设计用来向 HTML 页面添加交互行为。JavaScript 是一种脚本语言(脚本语言是一种轻量级的编程语言)。JavaScript 由数行可执行计算机代码组...
2015-11-12
Ajax教程之Ajax介绍
Ajax 由 HTML、JavaScript™ 技术、DHTML 和 DOM 组成,这一杰出的方法可以将笨拙的 Web 界面转化成交互性的 Ajax 应用程序。本文的作者是一位 Ajax 专家,他演示了这些技术如何协同工作 —— 从总体概述...
2015-11-12
bootstrap table之通用方法( 时间控件,导出,动态下拉框, 表单验证 ,选中与获取信息)代码分享
1.bootstrap-table 单击单行选中 $(&#x27;#gzrwTable&#x27;).on(&#x27;click-row.bs.table&#x27;, function(e, row, $element) { $(&#x...
2017-02-17
数据格式之战:JSON vs XML
在比较JSON和XML之前,我们先来上一堂关于数据格式的简要历史(更准确的说,是关于XML的始祖): 早在1970年,IBM开发了一种叫Generalized Markup Language的标记语言,简称GML,它主要是为脚本语言定义的一...
2016-01-13
JavaScript深入之类数组对象与
类数组对象 所谓的类数组对象: 拥有一个 length 属性和若干索引属性的对象 举个例子: var array = [&#x27;name&#x27;, &#x27;age&#x27;, &#x27;sex&#x27;]; var ...
2017-05-27
Vue.js原理分析之observer模块详解
介绍 observer是Vue核心中最重要的一个模块(个人认为),能够实现视图与数据的响应式更新,底层全凭observer的支持。 **注意:**本文是针对Vue@2.1.8进行分析 observer模块在Vue项目中的代码位置是src/c...
2017-03-16
javascript数据结构与算法之检索算法
查找数据有2种方式,顺序查找和二分查找。顺序查找适用于元素随机排列的列表。二分查找适用于元素已排序的列表。二分查找效率更高,但是必须是已经排好序的列表元素集合。 一:顺序查找 顺序查找是从列表的第一个元素开始对列表元素逐个进行判断,直到找到...
2017-03-22
Web缓存基础:术语、HTTP报头和缓存策略
简介 对于您的站点的访问者来说,智能化的内容缓存是提高用户体验最有效的方式之一。缓存,或者对之前的请求的临时存储,是HTTP协议实现中最核心的内容分发策略之一。分发路径中的组件均可以缓存内容来加速后续的请求,这受控于对该内容所声明的缓存策略...
2016-01-13
回到顶部