如何使Jersey使用GZip压缩作为响应消息体

Raf*_*ter 14 glassfish jax-rs jersey

我正在尝试编写一个简单的Jersey应用程序,它将文件从Jersey客户端发送到Jersey服务器并返回.但是,文件似乎只是在从客户端到服务器的路上编码而不是其他方式.我想知道如何改变这种行为.

我在一个简单的例子中测试它:

public class GZipEncodingTest extends JerseyTest {

  private static final String PATH = "/";
  private static final String QUESTION = "foo", ANSWER = "bar";
  private static final String ENCODING_GZIP = "gzip";

  @Path(PATH)
  public static class MyResource {
    @POST
    public Response handle(String question) throws IOException {
      assertEquals(QUESTION, question);
      return Response.ok(ANSWER).build(); // (1)
    }
  }

  @Override
  protected Application configure() {
    enable(TestProperties.LOG_TRAFFIC);
    enable(TestProperties.DUMP_ENTITY);
    return new ResourceConfig(MyResource.class, GZipEncoder.class);
  }

  @Override
  @SuppressWarnings("unchecked")
  protected void configureClient(ClientConfig config) {
    config.register(new EncodingFeature(ENCODING_GZIP, GZipEncoder.class));
  }

  @Test
  public void testHeaders() throws Exception {
    Response response = target().path(PATH).request().post(Entity.text(QUESTION));
    assertEquals(ANSWER, response.readEntity(String.class));
  }
}
Run Code Online (Sandbox Code Playgroud)

从记录的转储中,我可以看出请求是按预期的:内容编码在标头中发出信号并应用在请求消息正文中.在接受编码也被设置.服务器了解应用的gzip压缩并解压缩请求消息正文.但是,它忽略了客户端接受gzip压缩响应并发送未压缩响应消息体的事实.

当我追加encoding(ENCODING_GZIP)的线(1)Response-builder链,我得到我期待的结果.但是,我想仅在请求中将其标记为可接受时才应用编码.此外,我希望将此功能应用于整个范围,而不仅仅是针对特定的响应.

我当然可以手动添加这样一个功能WriterInterceptor:

public class GZipWriterInterceptor implements WriterInterceptor {
  @Override
  public void aroundWriteTo(WriterInterceptorContext context) 
      throws IOException, WebApplicationException {
    context.getHeaders().add(HttpHeaders.CONTENT_ENCODING, ENCODING_GZIP);
    context.proceed();
  }
}
Run Code Online (Sandbox Code Playgroud)

但我确信这是不必要的锅炉板.

EncodingFeature似乎只能是客户端库的一部分.我基本上正在寻找一种可能性,当请求通过accept-encoding建议编码时,Jersey服务器将数据编码为gzip.

当我尝试在网上搜索解决方案时,我发现了很多.他们中的大多数都关注Jersey 1.他们中的一些人建议为GrizzlyServer添加一个监听器(这将是Jersey特定而不是JAX-RS?).然后在Jersey 2依赖树中有很多类建议GZip编码:

  • org.glassfish.grizzly.http.GZipContentEncoding
  • org.glassfish.jersey.message.GZipEncoder
  • org.glassfish.grizzly.compression.zip.GZipEncoder
  • org.glassfish.grizzly.compression.zip.GZipDecoder
  • org.glassfish.grizzly.compression.zip.GZipFilter

我发现网络上的人建议使用它们中的任何一个,即使我认为这org.glassfish.jersey似乎是正确的选择,因为它是一个真正的泽西岛依赖.不要说ApacheConnector相关图书馆中的那些.我不知道我应该使用哪一个.

Raf*_*ter 20

我通过浏览泽西岛图书馆找到了它.对于服务器端,需要以下配置:

@Override
@SuppressWarnings("unchecked")
protected Application configure() {
    ResourceConfig resourceConfig = new ResourceConfig(MyResource.class);
    EncodingFilter.enableFor(resourceConfig, GZipEncoder.class);
    return resourceConfig;
}
Run Code Online (Sandbox Code Playgroud)

在转换下,EncodingFilter#enableFor(ResourceConfig.Class<? extends ContentEncoder>[])注册一个EncodingFilter和指定GZipEncoder的给定ResourceConfig.

我想在注册中绕道而行的原因在于任何编码都需要分两个阶段进行.首先,EncodingFilter(这是一个实际的ContainerResponseFilter通过设置修改响应的头Content-Encodinggzip.同时,过滤器不能由于过滤器被调用之前甚至创建此流.因此,流修改消息主体的实体流修改必须由WriterInterceptor处理过滤器之后以及创建实体流之后触发的处理.

出于这个原因,只有在客户端设置标头时才注册该GZipEncoder标志将用于请求解码,这与服务器的创建无关.Content-Encodinggzip

我用'GZipWriterInterceptor'给出的例子基本上是一个执行不佳的版本EncodingFilter.当然,标题应该设置在过滤器中,而不是设置在拦截器中.它在文档中说:

过滤器主要用于处理请求和响应参数,如HTTP头,URI和/或HTTP方法,而拦截器旨在通过操纵实体输入/输出流来操纵实体

因此,gzip编码不能简单地通过注册a激活GZipEncoder,它也需要注册过滤器.这就是为什么我希望两者都捆绑在一起Feature.

重要提示:EncodingFilter泽西岛内有两个班级.一个属于客户端,另一个属于服务器实现.不要使用错误的,因为他们做了根本不同的事情.不幸的是,在运行单元测试时,您将在类路径上拥有它们,因为它们依赖于客户端接口.