16

我正在尝试编写一个简单的 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 压缩并解压缩请求消息正文。但是,它忽略了客户端接受压缩响应并发送未压缩的响应消息体这一事实。

当我在-builder 链中添加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似乎只是客户端库的一部分。每当请求通过接受编码建议编码时,我基本上是在寻找一种可能性,使泽西服务器将数据编码为 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似乎是正确的选择,因为它是一个实际的 Jersey 依赖项。ApacheConnector更不用说在相关库中找到的那些了。我不知道我应该实际使用哪一个。

4

1 回答 1

24

I figured it out by looking through the Jersey library. For the server side, the following configuration is necessary:

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

Under the convers, EncodingFilter#enableFor(ResourceConfig.Class<? extends ContentEncoder>[])registers an EncodingFilter and the specified GZipEncoder with the given ResourceConfig.

I guess the reason behind this detour in the registration lies in the fact that any encoding needs to happen in two stages. First, the EncodingFilter (which is an actual ContainerResponseFilter modifies the header of the response by setting Content-Encoding to gzip. At the same time, a filter cannot modify the entity stream of the message body since the filter is invoked before this stream is even created. Therefore, the stream modification must be processed by a WriterInterceptor which is triggered after the processing of the filter and also after the creation of the entity stream.

For this reason, only registering the GZipEncoder will work for the request decoding when the Content-Encoding header is set to gzip by the client which occurs independently of the server's creation.

The example I gave with my 'GZipWriterInterceptor' is basically a poorly implemented version of the EncodingFilter. Of course, the header should be set in a filter and not in an interceptor. It says in the documentation:

Whereas filters are primarily intended to manipulate request and response parameters like HTTP headers, URIs and/or HTTP methods, interceptors are intended to manipulate entities, via manipulating entity input/output streams

Therefore, gzip encoding cannot simply be activated by registering a GZipEncoder, it needs to be registered with a filter, as well. This is why I expected both to be bundled in a Feature.

Important: There are two EncodingFilter classes within Jersey. One belongs to the client, the other belongs to the server implementation. Do not use the wrong one since they do fundamentally different things. Unfortunately, you will have both of them on your class path when running unit tests since they rely on the client interface.

于 2013-11-07T08:47:13.657 回答