一.问题阐述
最近做项目的时候遇到这么一个问题:
用 原生 Socket 进行 HTTP 请求的时候,添加了请求头1
Accept-Encoding: gzip
这个请求头表示的含义就是:返回的数据中会对响应体进行压缩,响应头不进行压缩(HTTP/1.1版)
如果服务器支持这种格式的压缩,那么返回的数据会如下这种格式1
2
3
4
5
6
7
8
9// 响应头不会压缩
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Encoding: gzip
Content-Type: text/html;charset=UTF-8
Date: Wed, 20 Feb 2019 06:19:04 GMT
// 响应体会进压缩
xxxxxxxxxx
服务器压缩的方式可能如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public static byte[] compress(String str, String encoding) {
if (str == null || str.length() == 0) {
return null;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream gzip;
try {
gzip = new GZIPOutputStream(out);
gzip.write(str.getBytes(encoding));//将字符串转为字节数组,对字节数组进行压缩
gzip.close();
} catch (IOException e) {
}
return out.toByteArray();//返回压缩后的字节流
}
正常情况下,如果请求头包含 gzip,响应时这种方式返回,那么在客户端接收到这种压缩的字节流,只有用同样的压缩流进行解压处理就可以得到数据,并且通常响应头都会包含如下的相应头1
2Content-Encoding: gzip
Content-Length: 13131
这表示返回的响应体是 gzip 格式的,并且字节流长度为 13131
一般情况是这样
但是在这样一种情况,如果返回的数据很大,或者数据量不确定(如一些动态网页),这个时候服务器就会选择对数据进行分段,并用一个16进制的数进行划分,表示一段的长度,如1
2
3
4
5
6
7
8
9
10
11
12
13HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Encoding: gzip
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked // 分段的数据就会返回这个响应头
Date: Wed, 20 Feb 2019 06:19:04 GMT
a3 // 16进制
xxxxx
5d9f
xxxxx
0 // 以 0 为结尾
这就使得响应头包含 gzip 和 chunked 的数据是一段经过分段的压缩流,因此也就不能简单地使用 GZIPInputStream 对数据进行处理
二.解决方法
对返回的字节流进行一个代理处理
1 | public class SegmentInputStream extends InputStream { |
详细的处理可以看 AppStore