经常抓包看 HTTP 请求的同学应该对 Vary 这个响应头字段并不陌生,它有什么用?用 PageSpeed 工具检查页面时,经常看到「Specify a Vary: Accept-Encoding header(请指定一个 Vary: Accept-Encoding 标头)」这样的建议,为什么要这样做?本文记录我对 Vary 的一些研究,其中就包含这些问题的答案。

HTTP 内容协商

要了解 Vary 的作用,先得了解 HTTP 的内容协商机制。有时候,同一个 URL 可以提供多份不同的文档,这就要求服务端和客户端之间有一个选择最合适版本的机制,这就是内容协商。

协商方式有两种,一种是服务端把文档可用版本列表发给客户端让用户选,这可以使用 300 Multiple Choices 状态码来实现。这种方案有不少问题,首先多一次网络往返;其次服务端同一文档的某些版本可能是为拥有某些技术特征的客户端准备的,而普通用户不一定了解这些细节。举个例子,服务端通常可以将静态资源输出为压缩和未压缩两个版本,压缩版显然是为支持压缩的客户端而准备的,但如果让普通用户选,很可能选择错误的版本。

所以 HTTP 的内容协商通常使用另外一种方案:服务端根据客户端发送的请求头中某些字段自动发送最合适的版本。可以用于这个机制的请求头字段又分两种:内容协商专用字段(Accept 字段)、其他字段。

首先来看 Accept 字段,详见下表:

请求头字段 说明 响应头字段
Accept 告知服务器发送何种媒体类型 Content-Type
Accept-Language 告知服务器发送何种语言 Content-Language
Accept-Charset 告知服务器发送何种字符集 Content-Type
Accept-Encoding 告知服务器采用何种压缩方式 Content-Encoding

例如客户端发送以下请求头:

Accept:*/*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:zh-CN,en-US;q=0.8,en;q=0.6

表示它可以接受任何 MIME 类型的资源;支持采用 gzip、deflate 或 sdch 压缩过的资源;可以接受 zh-CN、en-US 和 en 三种语言,并且 zh-CN 的权重最高(q 取值 0 - 1,最高为 1,最低为 0,默认为 1),服务端应该优先返回语言等于 zh-CN 的版本。

浏览器的响应头可能是这样的:

Content-Type: text/javascript
Content-Encoding: gzip

表示这个文档确切的 MIME 类型是 text/javascript;文档内容进行了 gzip 压缩;响应头没有 Content-Language 字段,通常说明返回版本的语言正好是请求头 Accept-Language 中权重最高的那个。

有时候,上面四个 Accept 字段并不够用,例如要针对特定浏览器如 IE6 输出不一样的内容,就需要用到请求头中的 User-Agent 字段。类似的,请求头中的 Cookie 也可能被服务端用做输出差异化内容的依据。

由于客户端和服务端之间可能存在一个或多个中间实体(如缓存服务器),而缓存服务最基本的要求是给用户返回正确的文档。如果服务端根据不同 User-Agent 返回不同内容,而缓存服务器把 IE6 用户的响应缓存下来,并返回给使用其他浏览器的用户,肯定会出问题 。

所以 HTTP 协议规定,如果服务端提供的内容取决于 User-Agent 这样「常规 Accept 协商字段之外」的请求头字段,那么响应头中必须包含 Vary 字段,且 Vary 的内容必须包含 User-Agent。同理,如果服务端同时使用请求头中 User-Agent 和 Cookie 这两个字段来生成内容,那么响应中的 Vary 字段看上去应该是这样的:

Vary: User-Agent, Cookie

也就是说 Vary 字段用于列出一个响应字段列表,告诉缓存服务器遇到同一个 URL 对应着不同版本文档的情况时,如何缓存和筛选合适的版本。

有 BUG 的缓存服务

再来看 PageSpeed 的「Specify a Vary: Accept-Encoding header」这个提示,按照上面的说明,Accept-Encoding 属于内容协商专用字段,服务端只需要在响应头中增加 Content-Encoding 字段,用来指明内容压缩格式;或者不输出 Content-Encoding 表明内容未经过压缩就可以了。而缓存服务器,应该针对不同的 Content-Encoding 缓存不同内容,再根据具体请求中的 Accept-Encoding 字段返回最合适的版本。

但是有些实现得有 BUG 的缓存服务器,会忽略响应头中的 Content-Encoding,从而可能给不支持压缩的客户端返回缓存的压缩版本。有两个方案可以避免这种情况发生:

  1. 将响应头中的 Cache-Control 字段设为 private,告诉中间实体不要缓存它;
  2. 增加 Vary: Accept-Encoding 响应头,明确告知缓存服务器按照 Accept-Encoding 字段的内容,分别缓存不同的版本;

通常为了更好的利用中间实体的缓存功能,我们都用第二种方案。

对于 css、js 这样的静态资源,只要客户端支持 gzip,服务端应该总是启用它;同时为了避免有 BUG 的缓存服务器给用户返回错误的版本,还应该输出 Vary: Accept-Encoding。

Nginx 和 SPDY

通常,上面说的这些工作,Web Server 都可以帮我们搞定。对于 Nginx 来说,下面这个配置可以自动给启用了 gzip 的响应加上 Vary: Accept-Encoding:

gzip_vary on;

用 curl 验证我博客的 js 文件,响应头如下:

jerry@www:~$ curl --head https://www.imququ.com/.../xx.jsHTTP/1.1 200 OK
Server: nginx
Date: Tue, 31 Dec 2013 16:34:48 GMT Content-Type: application/x-javascript Content-Length: 66748 Last-Modified: Tue, 31 Dec 2013 14:30:52 GMT Connection: keep-alive Vary: Accept-Encoding ETag: "52c2d51c-104bc" Expires: Fri, 29 Dec 2023 16:34:48 GMT Cache-Control: max-age=315360000 Strict-Transport-Security: max-age=31536000 Accept-Ranges: bytes 

可以看到,服务端正确输出了「Vary: Accept-Encoding」,一切正常。

但是用 Chrome 自带抓包工具看下,这个响应头却是这样:

HTTP/1.1 200 OK
cache-control: max-age=315360000
content-encoding: gzip
content-type: application/x-javascript
date: Tue, 31 Dec 2013 16:35:27 GMT expires: Fri, 29 Dec 2023 16:35:27 GMT last-modified: Tue, 31 Dec 2013 14:30:52 GMT server: nginx status: 200 strict-transport-security: max-age=31536000 version: HTTP/1.1 

我的博客支持 SPDY/2 协议,用 Chrome 访问我博客会走 SPDY,所以上面的响应头看上有点不同寻常,例如字段名都变成了小写;多了 status、version 等字段,这些变化下次专门介绍(注:见「SPDY 3.1 中的请求 / 响应头」)。神奇的是尽管服务端没任何变化,但响应中的 Vary: Accept-Encoding 却不见了。

SPDY 规定客户端必须支持压缩,这意味着 SPDY 服务器可以直接启用压缩而不用关心请求头中的 Accept-Encoding 字段。下面这段来自 Nginx 支持的 SPDY/2 协议:

User-agents are expected to support gzip and deflate compression. Regardless of the Accept-Encoding sent by the user-agent, the server may select gzip or deflate encoding at any time. [via]

于是,对于支持 SPDY 的客户端来说,Vary: Accept-Encoding 没有用途,Nginx 选择直接去掉它,可以节省一点流量。curl 或其他不支持 SPDY 协议的客户端还是走 HTTP 协议,所以看到的响应头是常规的。

Nginx 的这个做法是否合适一直有争论,实际上并不是所有支持 SPDY 的 Web Server 都会这么做。例如即使通过 SPDY 协议访问 Google 首页的 js 文件,依然可以看到 vary: Accept-Encoding:

HTTP/1.1 200 OK
status: 200 OK
version: HTTP/1.1
age: 25762 alternate-protocol: 443:quic cache-control: public, max-age=31536000 content-encoding: gzip content-length: 154614 content-type: text/javascript; charset=UTF-8 date: Tue, 31 Dec 2013 23:23:51 GMT expires: Wed, 31 Dec 2014 23:23:51 GMT last-modified: Mon, 16 Dec 2013 21:54:35 GMT server: sffe vary: Accept-Encoding x-content-type-options: nosniff x-xss-protection: 1; mode=block 

另外,现阶段 Chrome 和 Firefox 都支持 SPDY 协议,但 PageSpeed Chrome 版和 Firefox 版都没有针对 SPDY 协议做特别处理,所以用它们测试我的博客,还是会提示「Specify a Vary: Accept-Encoding header」,这有点让人哭笑不得。不过 PageSpeed 在线版 已经更新规则,估计扩展版也快了。

PS:Vary 在 IE 下有很多坑,使用时要格外小心。网上这部分文章比较多,例如 hax 早年写的 IE 与 Vary 头,可以点过去了解下。

本文链接:https://www.imququ.com/post/vary-header-in-http.html

HTTP 协议中 Vary 的一些研究相关推荐

  1. HTTP 协议中 Vary 的一些研究 转自https://www.imququ.com/post/vary-header-in-http.html

    经常抓包看 HTTP 请求的同学应该对 Vary 这个响应头字段并不陌生,它有什么用?用 PageSpeed 工具检查页面时,经常看到「Specify a Vary: Accept-Encoding ...

  2. HTTP 协议中 Vary

    转自https://imququ.com/post/vary-header-in-http.html HTTP 协议中 Vary 的一些研究 文章目录 HTTP 内容协商 有 BUG 的缓存服务 Ng ...

  3. Http协议中Get和Post的浅谈

    起名困难户,每次写文章最愁的就是不知道该如何起个稍具内涵的名字,如果这篇文章我只是写写Get和Post的区别,我可以起个名字"Get和Post的那点事",如果打算阐述一下Http协 ...

  4. 权益证明协议中的拜占庭容错

    如果您遵循以太坊基金会的Casper研究,您将会听到许多术语,如"异步安全性"和"异步时序模型中的容错阈值".我想概述基础拜占庭容错理论中的重要概念因此您可以在 ...

  5. rto初始化和计算_TCP系列13—重传—3、协议中RTO计算和RTO定时器维护

    从上一篇示例中我们可以看到在TCP中有一个重要的过程就是决定何时进行超时重传,也就是RTO的计算更新.由于网络状况可能会受到路由变化.网络负载等因素的影响,因此RTO也必须跟随网络状况动态更新.如果T ...

  6. HTTP协议中你必须知道的三种数据格式

    实习中的一个主要工作就是分析 HTTP 中的协议,自己也用 Python 写过正则表达式对 HTTP 请求和响应的内容进行匹配,然后把关键字段抽离出来放到一个字典中以备使用(可以稍微改造一下就是一个爬 ...

  7. http协议中content-length 以及chunked编码分析

    转载请注明出处 http://blog.csdn.net/yankai0219/article/details/8269922 0.序 1.http/1.1协议中与chunked编码的相关字段 1)E ...

  8. HTTP 协议中的 Transfer-Encoding

    HTTP 协议中的 Transfer-Encoding 文章目录 Persistent Connection Content-Length Transfer-Encoding: chunked 本文作 ...

  9. C#的HTTP协议中POST与GET的区别

    引言 HTTP协议我想任何IT人士都耳熟能详了,大家都能说出个所以然来.但是如果我问你HTTP协议的请求方法有哪些?POST与GET的差异?GET或POST传送数据量的大小有限制吗?HTTP响应的状态 ...

最新文章

  1. R语言ggplot2可视化在可视化的接种中插入图片、添加图片实战
  2. 验证mongodb副本集并实现自动切换primary~记录过程
  3. java swing面试题_Java面试题之AWT、Swing
  4. 【深度学习】煮酒论英雄:深度学习CV领域最瞩目的top成果总结
  5. 会导致所有者权益减少的项目是_处置固定资产而发生的净损失为什么会导致所有者权益减少...
  6. VMware下Centos7快速搭建vsftpd
  7. 贪心——区间选点问题(用最少数量的箭引爆气球 Leetcode 452)
  8. Laravel测试驱动开发--功能测试 1
  9. android 使用so库,Android 使用SO库
  10. 拓端tecdat|R语言可视化:ggplot2冲积/桑基图sankey分析大学录取情况、泰坦尼克幸存者数据
  11. 将List按照指定大小等分的几种实现方式和效率对比及优化
  12. xxnet 360浏览器设置
  13. 在MFC中的socket编程
  14. AI Text Classifier,OpenAI自家产品,我检测我自己
  15. 360公司开源Atlas快速上手安装配置
  16. 知道这些,让你在使用Prism 8 时更加得心应手!
  17. python 'utf-8' codec can't decode byte 0xca in position 21: invalid continuation byte
  18. 病毒预报:Trojan_Generic.F
  19. 自我管理能力提升-角色定位
  20. 欧洲斯托克 50 指数

热门文章

  1. 远程服务器任务,远程大批量操作windows服务器的计划任务
  2. jsp判断json第一位_java 判断字符串是否为json格式
  3. python数据结构编程_写给Python编程高手之 数据结构
  4. 基本查询(Query查询中文)
  5. 中国行业趋势报告——2022年度特别报告
  6. 2020微博用户发展报告
  7. 抖音城市美食算数报告
  8. 空字符是什么类型_Redis是什么?看这一篇就够了!
  9. pythonweb开发工作内容_我的第一个python web开发框架(5)——开发前准备工作(了解编码前需要知道的一些常识)...
  10. 2015年《大数据》高被引论文Top10文章No.4——关于大数据交易核心法律问题 —— 数据所有权的探讨...