记一次 UnzipError: incorrect header check...错误分析
今天前端小伙伴在调试前端项目遇到一个问题,简单分析记录下.
项目背景
前端项目基于Alibaba 的eggjs 构建的SSR项目.
Api请求通过
Nginx
反向代理到Gateway(Zuul v2.1.1RELEASE + nacos 1.0)
相关js
import { Service } from 'beidou';
export default class SimpleService extends Service {
async get(){
const result = await this.app.curl('http://service.demo.com/api/querySomething', {
method: 'GET',
dataType: 'json',
data: {id:1}
});
}
}
访问页面时NodeJs server 抛出如下异常
{ UnzipError: incorrect header check, GET http://service.demo.com/api/querySomething 200 (connected: true, keepalive socket: false, agent status: {"createSocketCount":1,"createSocketErrorCount":0,"closeSocketCount":0,"errorSocketCount":0,"timeoutSocketCount":0,"requestCount":0,"freeSockets":{},"sockets":{"testgate.feewee.cn:443::::::::::::::::":1},"requests":{}}, socketHandledRequests: 1, socketHandledResponses: 1) |
上述异常是在请求/api/querySomething
这个接口,处理响应内容时,出现gzip 解压错误.在浏览器里测试了下这个接口,接口是有正确返回。
分析
Gzip
的文件头的前两个字节是1F 8B
,上述响应头出现了content-encoding: gzip
,表示内容使用了gzip
压缩,但是上述接口返回的内容体 并不是gzip
格式的。所以导致了gzip解压
失败。
为了知道请求时具体发生了什么,使用java 模拟了下这个接口调用
public static void test2() throws IOException { |
测试输出结果如下
HTTP/1.1 200 OK |
再次测试,把请求头加上Accept-encoding:gzip
测试输出结果如下
HTTP/1.1 200 OK |
第一次请求返回的内容非gzip,第二次的请求返回内容已被gzip压缩过(上述乱码部分是 gzip 的相关标识字节序)
结论
nginx server 每次的响应头都返回了
Content-Encoding: gzip
只对请求头中出现了
Accept-encoding:gzip
的请求才会压缩响应内容.
通过Wireshark 抓包发现,前端node server 发起的请求的头中没有 Accept-encoding:gzip
.
备注:
- nginx 已开启
gzip
gzip on;- wireshark 过滤指定ip的数据包
ip.addr == target ip address
至于为啥请求头中没有Accept-encoding:gzip
,nginx 还是返回 Content-Encoding: gzip
,只有研究下nginx的gzip模块源码才知道了(有机会补充)
!!! 上面的锅,Nginx 不背 !!!
补充(2019年5月28日)
研究了下发现,最终问题出在Zuul 网关。
分析Zuul网关源码发现,Zuul会把upstream服务器的响应头全部返回给客户端,即使内容非GZIP.
相关代码在 SendResponseFilter#addResponseHeader
这个类做了两件事
添加响应头
写响应内容
public void run() { |
//添加响应头 |
最新的master分支已经修复这个问题了.
相关Issue:Don’t set content-encoding header when un-gzipping
吐槽下:这个问题至最近才被人发现解决,去年在项目实际开发中就遇到了这个隐藏的bug(处理鹰眼围栏通知回调),迫于没有仔细研究,当时采用了其他方法解决,真是应了那句话 “天道好轮回,苍天饶过谁”
如何解决
前端在访问api时加上如下配置
async get(){
const result = await this.app.curl('http://service.demo.com/api/querySomething', {
gzip: true,//当开启改选项时,请求头会自动添加`Accept-encoding:gzip`,默认关闭
method: 'GET',
dataType: 'json',
data: {id:1}
});
}
上述curl使用的js库为urllib
等spring-cloud-neflix 发布最新release,或者拉取最新的master分支源码本地install.
使用spring-cloud-gateway