Jersey开启Gzip踩的坑

最近给一个基于Jerysey的Restful项目增加gzip功能,通过自动代码提示发现Jersey自带了一个GZipEncoder,通过注释看,只要头CONTENT_ENCODING为gzip或x-gzip就可以进行压缩和解压。
于是在ResourceConfig里直接注册了GzipEncoder。

1
register(GzipEncoder.class);

但在测试的时候发现,请求压缩后decode没问题,但返回的响应则无法encode,也就是无法压缩,一开始以为是ACCEPT_ENCODING问题,但设置了也不对,最后debug发现根本进不去GzipEncoder#encode方法。
测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
CloseableHttpClient client = HttpClients.createDefault();
StringEntity entity = new StringEntity("{\"id\":\"123\"}", ContentType.create(
ContentType.APPLICATION_JSON.getMimeType(), "UTF-8"));
entity.setContentEncoding("UTF-8");
GzipCompressingEntity gc=new GzipCompressingEntity(entity);
String out = null;
try {
// send
HttpPost post = new HttpPost("http://127.0.0.1:8080/services/test/v1/x");
try {
post.addHeader("Accept-Encoding","gzip");
post.setEntity(gc);
CloseableHttpResponse response = null;
try {
response = client.execute(post);
int code = response.getStatusLine().getStatusCode();
if (code != HttpStatus.SC_OK) {
throw new BizSystemException("发送请求失败,返回的状态码:" + code);
}
HttpEntity respEntity = response.getEntity();
// consume
out = EntityUtils.toString(respEntity, "UTF-8");
EntityUtils.consume(respEntity);
} catch (Exception e) {
log.error("发送请求异常", e);
throw new BizSystemException("发送请求失败", e);
} finally {
IOUtils.closeQuietly(response);
}
} finally {
post.releaseConnection();
}
} finally {
IOUtils.closeQuietly(client);
}

最后google了一下发现启用GzipEncoder压缩响应属于entity-filtering,需要开启entity-filtering,于是引入类库:

1
2
3
4
5
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-entity-filtering</artifactId>
<version>2.21</version>
</dependency>

并且开启entity-filtering功能,以及激活gzipencoder:

1
2
register(EntityFilteringFeature.class);
EncodingFilter.enableFor(this, GZipEncoder.class);

通过日志看已经可以正确处理请求和响应的压缩。
使用时还需要注意的时GZipEncoder的优先级为:

1
2
3
4
@Priority(Priorities.ENTITY_CODER)
public class GZipEncoder extends ContentEncoder{
...
}

也就是4000,如果有自定义的过滤器/拦截器需要注意下顺序。

比较坑的是看源码GZIPEncoder仅仅是一个拦截器,按照以往的经验注册一下调整下顺序就应该是开箱即用的,为毛会和Entity-Filtering扯上关系,还要多引一个依赖,根据官网Entity-Filtering的介绍来看:

Support for Entity Filtering in Jersey introduces a convenient facility for reducing the amount of data exchanged over the wire between client and server without a need to create specialized data view components. The main idea behind this feature is to give you APIs that will let you to selectively filter out any non-relevant data from the marshalled object model before sending the data to the other party based on the context of the particular message exchange. This way, only the necessary or relevant portion of the data is transferred over the network with each client request or server response, without a need to create special facade models for transferring these limited subsets of the model data.

Entity-Filtering功能最大的作用就是减少传输间的显示组件(view components),也就是说往往在客户端与服务器端的数据交换都需要定义很多JavaBean用来序列化请求/响应,哪怕有很多无关的数据需要组合到一起都要定义一个根的JavaBean用于组合它们(Facade模式)。

Entity-Filtering可以在传输的时候对数据进行处理,避免创建这些Facade对象。

所以究竟为啥一个拦截器的工作需要和Entity-Filtering有关联呢。😂

参考:

1.how-to-compress-responses-in-java-rest-api-with-gzip-and-jersey