httpclient Accept-Encoding 乱码[通俗易懂]

httpclient Accept-Encoding 乱码[通俗易懂]解决方法1HttpEntityhttpEntity=httpResponse.getEntity();2if(httpEntity!=null){3if(httpEntity.getContentEncoding()!=null){4if("g…

大家好,又见面了,我是你们的朋友全栈君。

解决方法

 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 头,可以点过去了解下。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/159698.html原文链接:https://javaforall.net

(0)
上一篇 2022年7月15日 上午11:36
下一篇 2022年7月15日 上午11:46


相关推荐

  • IP地址和子网掩码的关系, 如何计算网络地址?

    IP地址和子网掩码的关系, 如何计算网络地址?一 IP 地址电脑之间要实现网络通信 就必须要有一个合法的 ip 地址 IP 地址 网络地址 主机地址 又称 主机号和网络号组成 ip 地址的结构使我们可以在 Internet 上很方便的寻址 ip 地址通常用更直观的 以圆点分隔号的四个十进制数字表示 每个数字从 0 到 255 如某一台主机的 ip 地址为 128 20 4 1 在局域网里 同样也需要 ip 地址 一般内网的 ip 地址是以 192 168 开头的 这样

    2026年3月26日
    1
  • 主流内存数据库功能特性和性能比较

    主流内存数据库功能特性和性能比较内存数据库从范型上可以分为关系型内存数据库和键值型内存数据库 在实际应用中内存数据库主要是配合 oracle 或 mysql 等大型关系数据库使用 关注性能 作用类似于缓存 并不注重数据完整性和数据一致性 基于键值型的内存数据库比关系型更加易于使用 性能和可扩展性更好 因此在应用上比关系型的内存数据库使用更多 本文首先比较 FastDB Memcached 和 Redis 主流内存数据库的功能特性 再从性能上比较

    2026年3月16日
    2
  • php ecshop二次开发,ecshop二次开发对ecshop系统框架分析

    php ecshop二次开发,ecshop二次开发对ecshop系统框架分析ecshop 二次开发对 ecshop 系统框架分析第一 整体框架结构 整个系统是层结构有数据层有外观层比如我们 index php 是外观层 和用户打交道的层 那推荐产品来说吧在 index php 是对数据层函数的调用 get recommend goods new 这个函数那么他是在 include 目录下的 lib goods php 进行定义既数据层 第二 重要目录 首先

    2026年3月19日
    2
  • marquee用到的属性

    marquee用到的属性一、marquee标签的几个重要属性:1.direction:滚动方向(包括4个值:up、down、left、right)说明:up:从下向上滚动;down:从上向下滚动;left:从右向左滚动;

    2022年7月2日
    28
  • 一级域名301重定向到www二级域名

    一级域名301重定向到www二级域名301重定向有利于百度的搜索例如一个域名www.test.com如果不做设置会产生4个网址,(1)test.com(2)www.test.com(3)test.com/default.html(4)www.test.com/default.html会导致网址的PR值被分散.htaccess设置Options+FollowSymLinksIndexIgnore*/*…

    2022年5月21日
    60
  • android toast点击事件_android生命周期七种方法

    android toast点击事件_android生命周期七种方法设置AndroidToast持续时间非常长(例如1分钟)(SetAndroidToastdurationtobereallylong(e.g.,1minute))我尝试将我的Toast节目时间设置为1分钟。我试试这个:finalToasttoast=Toast.makeText(getApplicationContext(),”MESSAGE”,Toast.LE…

    2025年11月4日
    5

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号