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

时间:2013-11-04 10:17:11

标签: glassfish jersey jax-rs

我正在尝试编写一个简单的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));
  }
}

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

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

我当然可以使用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();
  }
}

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

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相关库中的那些。我不知道我应该使用哪一个。

1 个答案:

答案 0 :(得分:19)

我通过浏览泽西岛图书馆想出来了。对于服务器端,需要以下配置:

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

在转化后,EncodingFilter#enableFor(ResourceConfig.Class<? extends ContentEncoder>[])EncodingFilter注册GZipEncoder,并在指定的ResourceConfig注册EncodingFilter

我想在注册中绕道而行的原因在于任何编码都需要分两个阶段进行。首先,ContainerResponseFilter(实际Content-Encoding通过将gzip设置为WriterInterceptor来修改响应的标头。同时,过滤器无法修改实体流由于在甚至创建此流之前调用了过滤器,因此必须通过在处理过滤器之后以及在创建实体流之后触发的GZipEncoder来处理流修改。

出于这个原因,只有当Content-Encoding标头由客户端设置为gzip时,只有注册EncodingFilter才能用于请求解码,这与服务器的创建无关。

我使用'GZipWriterInterceptor'给出的示例基本上是GZipEncoder的实现版本。当然,标题应该设置在过滤器中,而不是设置在拦截器中。它says in the documentation

  

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

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

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