Spring Gzip压缩

输出Gzip压缩

在SpringBoot项目中启用输出Gzip压缩,需要添加如下配置。

1
2
3
4
5
6
7
8
9
10
11
12
server:
compression:
enabled: true
min-response-size: 2048
mime-types:
- application/json
- application/x-www-form-urlencoded
- application/xml
- text/html
- text/xml
- text/plain
- application/javascript

是否压缩取决于数据大小是否达到min-response-size配置的值且请求方在request header中是否添加Accept-Encoding:gzip,deflate, 一般浏览器会在请求头中默认添加该header。

若提供接口给外部服务,若有使用Nginx,可以通过Nginx反向代理转发到我们的WEB服务器时在请求头中添加Accept-Encoding:gzip,deflate

验证GZIP是否生效

  1. 通过HttpClient的方法验证

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    HttpClient httpClient = new DefaultHttpClient();
    HttpGet get = new HttpGet(uri);
    ResponseHandler<String> responseHandler = new BasicResponseHandler();
    try {
    get.setHeader("Accept-Encoding", "gzip,deflate");
    String content = httpClient.execute(get, responseHandler);
    System.out.println(content); // 如果gzip生效,会打印出乱码

    HttpResponse response = httpClient.execute(get);
    long cLen = response.getEntity().getContentLength();
    System.out.println(cLen); // 如果gzip生效,长度值为-1或比原始大小小很多的值
    } catch(Exception e) {
    // ignore ...
    } finally {
    httpClient.getConnectionManager().shutdown();
    }
  2. 通过浏览器调试工具对比Network中请求的Size

输入Gzip压缩

对于请求体比较大的接口,通常会采用压缩的方式进行传输。这里对Gzip踩坑进行一下总结。

对于即支持Gzip压缩调用,也支持非压缩调用的接口,通常做法是在请求头中放入一个字段来判断该字段的值来确定所走流程。通常做法可能很多人会采用标准的请求头参数Content-Encoding,在直接调用不仅过zuul的服务中是没有问题的。但是在有zuul的服务中,zuul默认会将请求头中的Content-Encoding移除,从而导致获取不到Content-Encoding该字段,从而走非Gzip的流程导致bug,在该种情况下最好使用自定义的请头代替标准的请去头。StackOverFlow参考

其次对于程序对Gzip的处理,可能对于很多接口一般都可能是使用@RequestBody将请求体中的内容直接取出来,客户端使用如下方式进行压缩,再将压缩后的内容通过StringEntity放入HttpPost的请求体中:

1
2
3
4
5
6
7
8
9
10
public static String compress(String param) throws IOException {
if (null == param || param.length() <= 0) {
return param;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(out);
gzip.write(param.getBytes("utf-8"));
gzip.close();
return out.toString("ISO-8859-1");
}

在服务端将通过@RequestBody获取到的请求体通过如下方式解压出来使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16


public static String unCompress(String paramGzip) throws IOException {
if (null == paramGzip || paramGzip.length() <= 0) {
return paramGzip;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(paramGzip.getBytes("ISO-8859-1"));
GZIPInputStream gzip = new GZIPInputStream(in);
byte[] buffer = new byte[256];
int n = 0;
while ((n = gzip.read(buffer)) >= 0){
out.write(buffer, 0, n);
}
return out.toString("utf-8");
}

以上方式的问题在于不通用,如果说使用Python或者其他语言来,或者说如JMeter和LoadRunner之类的工具请求,基本上百分之百会乱码导致请求失败。

优化方案,直接从HttpServletRequest中获取InputStream从而获取到字节数组。并将字节数组解压缩,最后将解压缩后的字节数组转成字符串进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static byte[] unCompressBytes(byte[] bytes) throws IOException {
if (null == bytes || bytes.length <= 0) {
return bytes;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
GZIPInputStream gzip = new GZIPInputStream(in);
byte[] buffer = new byte[256];
int n = 0;
while ((n = gzip.read(buffer)) >= 0) {
out.write(buffer, 0, n);
}
gzip.close();
return out.toByteArray();
}

对于Java客户端的请求,可以直接使用GzipCompressingEntity标准的请求方式来调用:

1
2
3
4
5
6
7
8
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-Type", "application/json;charset=UTF-8");
httpPost.setHeader("AA-Content-Encoding", "gzip");

StringEntity entity = new StringEntity(param, "UTF-8");
httpPost.setEntity(new GzipCompressingEntity(entity));

HttpResponse httpResponse = httpClient.execute(httpPost);

当然也可以自己来压缩调用,通过compressByte方法将数据压缩成字节流,再将字节流直接放入到HttpPost的请求体中,中间不要做任何转码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static byte[] compressByte(String param) throws IOException {
if (null == param || param.length() <= 0) {
return new byte[0];
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(out);
gzip.write(param.getBytes(StandardCharsets.UTF_8));
gzip.close();
return out.toByteArray();
}


HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-Type", "application/json;charset=UTF-8");
httpPost.setHeader("AA-Content-Encoding", "gzip");

ByteArrayEntity entity = new ByteArrayEntity(bytes);
httpPost.setEntity(new GzipCompressingEntity(entity));

HttpResponse httpResponse = httpClient.execute(httpPost);

对于Python的调用也很简单:

1
2
3
4
5
6
7
8
headers = {
"Content-Type": "application/json;charset=utf-8",
"AA-Content-Encoding": "gzip"
}

dataGzip = gzip.compress(json.dumps(data).encode("utf-8"))

response = requests.post(url=url, headers=headers, data=dataGzip, params=params)