运行环境:Docker,JDK8,Spring Boot,Spring Cloud,Zuul
在微服务网关编写的过程中,需要解析网关转发后响应的报文信息,需要对 RequestContext.getCurrentContext().getResponseDataStream() 的流数据进行解析。
在本地环境及测试环境均运行正常,而在正式环境则出现响应解析乱码的问题。
尝试使用
InputStream stream = ctx.getResponseDataStream();
bytes = StreamUtils.copyToByteArray(stream);
log.info(new String(bytes,"utf8"));
log.info(new String(bytes,"gbk"));
均展示乱码。而同样的代码在本地及测试均正常。
无奈将响应头打印出来,如下:
X-Zuul-Service: http://***:***/***/ ,Server: Apache-Coyote/1.1 ,Content-Type: text/html;charset=UTF-8 ,Date: Mon, 19 Nov 2018 12:16:25 GMT ,Content-Encoding: gzip ,Vary: Accept-Encoding ,Transfer-Encoding: chunked ,Connection: Keep-alive ,Via: 1.1 ID-0002262016416074 uproxy-9 ,
重点在于 Content-Encoding: gzip
Accept-Encoding 和Content-Encoding是HTTP中用来对采用哪种编码格式传输正文进行协定的一对头部字段。
工作原理如下:
1.首先浏览器(也就是客户端)发送请求时,通过Accept-Encoding带上自己支持的内容编码格式列表;
2.服务端在接收到请求后,从中挑选出一种用来对响应信息进行编码,并通过Content-Encoding来说明服务端选定的编码信息
3.浏览器在拿到响应正文后,依据Content-Encoding进行解压。
服务端也可以返回未压缩的正文,但这种情况不允许返回Content-Encoding
内容编码的目的是优化传输内容的大小,通俗的讲就是尽心压缩。一般经过gzip压缩过的文本响应,只有原始大小的1/4(这个数据我现在还不确定)。对于文本类响应是否开启了内容压缩,是我们做性能优化时首先要检查的重要项目;而对于JPG/PNG这类本身已经高度压缩过的二进制文件,不推介开启内容压缩,效果几乎微乎其微,还浪费cpu
内容的编码针对的只是传输正文。在HTTP/1中,头部始终是以ASCII文本传输,没有经过任何压缩。不过这个问题在HTTP/2中已经解决。
内容编码使用特别广泛,理解起来也特别简单,但是要注意的是不要把它与HTPP中的另外一个概念:传输编码Transfer-Encoding搞混即可。
引用:HTTP 协议中的Content-Encoding - 枫秀天涯的博客 - CSDN博客
所以乱码的根本原因是响应数据被压缩,直接解析理所当然的就会乱码了。普通的InputStream 解析可以参考下面的引用:
引用:HttpCLient实现对被GZip压缩过的Response进行解压 - 苏文星 - CSDN博客
参考上述引用,自己编写在Zuul进行处理的代码逻辑如下:
InputStream stream = ctx.getResponseDataStream();
byte[] bytes;
if(ctx.getResponseGZipped()) {
log.info(" it is gzipped ");
GZIPInputStream gzipInputStream = new GZIPInputStream(stream);
bytes = StreamUtils.copyToByteArray(gzipInputStream);
} else {
log.info(" it isn't gzipped ");
bytes = StreamUtils.copyToByteArray(stream);
}
body = new String(bytes,"utf8");
ctx.setResponseBody(body);
最终成功解决问题。
至于为什么会出现这种问题,可能是因为正式环境的F5负载,或者是HA负载的过程中,给请求添加了额外的头部数据。
本地无负载,测试环境为HA负载,正式环境有F5负载与HA负载。