解决方法

 1 HttpEntity httpEntity = httpResponse.getEntity();
 2             if (httpEntity != null) {
 3                 if (httpEntity.getContentEncoding() != null) {
 4                     if ("gzip".equalsIgnoreCase(httpEntity.getContentEncoding().getValue())) {
 5                         httpEntity = new GzipDecompressingEntity(httpEntity);
 6                     } else if ("deflate".equalsIgnoreCase(httpEntity.getContentEncoding().getValue())) {
 7                         httpEntity = new DeflateDecompressingEntity(httpEntity);
 8                     }
 9                 }
10                 htmlByte = EntityUtils.toByteArray(httpEntity);
11             }

来自:https://www.imququ.com/post/vary-header-in-http.html

经常抓包看 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.js
HTTP/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 头,可以点过去了解下。

httpclient Accept-Encoding 乱码相关推荐

  1. [HttpClient]HTTPClient PostMethod 中文乱码问题解决方案(2种)

     HTTPClient PostMethod 中文乱码问题解决方案(2种) Apache HttpClient ( http://jakarta.apache.org/commons/httpcl ...

  2. r语言读取csv文件encoding乱码问题

    问题描述: 用read.csv('data.csv',encoding = 'UTF-8')读取后中文仍然乱码 解决步骤: 1.查看语言环境,发现是英文 Sys.getlocale(category ...

  3. Python:爬虫乱码

    文章目录 一.预备知识 进制 位(bit)与字节(Byte) 编码/解码 二.编解码方式(以文本/字符串编解码为例) 规则 1. ASCII字符集--ASCII编/解码 2. GBK字符集--GBK编 ...

  4. JavaWeb开发 —— Web入门

    目录 一.Spring 二.SpringBootWeb快速入门 三.HTTP协议 1.  概述 2.  请求协议 3.  响应协议 四.Web服务器 - Tomcat 1.  介绍 2.  基本使用 ...

  5. Python 爬虫篇#笔记02# | 网页请求原理 和 抓取网页数据

    目录 一. 网页请求原理 1.1 浏览网页的过程 1.2 统一资源定位符URL 1.3 计算机域名系统DNS 1.4 分析浏览器显示完整网页的过程 1.5 客户端THHP请求格式 1.6 服务端HTT ...

  6. Linux Wifi共享上网脚本,类似win 7的Connectify工具

    翊轩LeOn 黑夜给了我黑色的眼睛,而我却用它来寻找光明. 目录视图 摘要视图 订阅 CSDN学院讲师招募,诚邀您加入!    博客Markdown编辑器上线啦    PMBOK第五版精讲视频教程   ...

  7. 爱企查 爬取公司基本信息

    前言: 前几天老板让我爬下制造业相关的公司,最开始说用企查查,但是你懂的,6页之后就要收费了,不能白嫖.就是前6页页不好爬,超频访问阻拦着我.但是VIP似乎可以直接导出,不用爬虫,遂和老板交涉,哪怕花 ...

  8. Python爬虫(1)------爬取网站图片

    初学爬虫的学习流程 环境 python 3.6 使用 urlib库进行爬取内容 熟悉爬虫 首先对百度进行爬取 # -*- coding: utf-8 -*- import urllib.request ...

  9. 蚂蚁金服 java电话面_蚂蚁金服前端电话面试(一面)

    1. 自我介绍 2. 介绍项目相关:如何技术选型,遇到最大的问题,如何解决问题,哪个项目让我学到最多,印象最深刻之类的. 3. 性能优化有哪些原则措施,减少http请求为什么能提高性能?有没具体实践过 ...

  10. http 错误 404.0 - not found_python3从零学习-5.10.8、http.client—HTTP 协议客户端

    源代码: Lib/http/client.py 这个模块定义了实现 HTTP 和 HTTPS 协议客户端的类. 它通常不直接使用 - 模块 urllib.request 用它来处理使用 HTTP 和 ...

最新文章

  1. python txt文件读写(追加、覆盖)
  2. pytorch bert文本分类_一起读Bert文本分类代码 (pytorch篇 四)
  3. 谨记!怎么样的编程会让你进监狱?
  4. 青龙羊毛——灰兔掌赚吹牛逼
  5. beego 注解路由无效问题分析
  6. 重写 View 的 Touch 方法,实现一个酷炫的九宫格图片
  7. Redis集群:sharding策略
  8. android手机用户收入,苹果与安卓用户特征对比 iPhone用户高收入占多
  9. 软件开发中的V字模型与W模型(使开发与侧试分离)
  10. android getLong()用法
  11. js定义php中变量,JavaScript 变量
  12. JAVA字符串排序去重
  13. 完美Vista 自动激活安装版下载
  14. 定制QWidget标题栏的示例
  15. 企业级BOM项目建设概况
  16. 微信小程序实现拼团成功动画
  17. ETH持续暴雷!历史是如此的相似
  18. 浏览器免费安装ChatGPT插件与国内免费ChatGPT分享
  19. ABAP 获取屏幕字段的值 搜索帮助联动动态查询SAP
  20. 如何调用openai

热门文章

  1. 使用 Go 语言进行并发编程的实践方法
  2. python 头条号_python爬取今日头条收藏目录
  3. 定语状语异同 限定和修饰关系 句子结构(修饰和被修饰)
  4. AI遮天传 DL-反馈神经网络RNN
  5. 版本管理-SVN分支,合并,切换
  6. 隐藏自己的Linux内核模块
  7. 【JS】Proxy(代理)
  8. 分布式--生成数据库全局唯一ID--方法/方案
  9. 09 | 不可或缺的自定义函数
  10. intel神经网络压缩库distiller使用时遇到的问题