Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make Jersey use GZip compression for the response message body

I am trying to write a simple Jersey application that sends files from a Jersey client to a Jersey server and back. However, the files only seem to be encoded on the way from the client to the server but not the other way. I wonder how I can change this behavior.

I am testing this in a simple example:

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));
  }
}

From the logged dump, I can tell that the request is as intended: the content encoding is signaled in the header and applied on the request message body. The Accept-Encoding is also set. The server understands the applied gzip compression and unzips the request message body. However, it ignores the fact that the client accepts a gzipped response and sends the response message body uncompressed.

When I append encoding(ENCODING_GZIP) in line (1) in the Response-builder chain, I get the result I am looking for. However, I want to only apply the encoding if it was marked as acceptable in the request. Furthermore, I want to ally this feature application wide and not only for specific responses.

I can of course add such a feature manually with a 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();
  }
}

but I am convinced that this is unnecessary boiler plate.

The EncodingFeature seems to only be a part of the client library. I am basically looking for a possibility to make the Jersey server encode data as gzip whenever the request suggested the encoding via accept-encoding.

When I try to search for solutions on the web, I find plenty. Most of them concern Jersey 1. Some of them suggest adding a listener to the GrizzlyServer (which would be Jersey specific and not JAX-RS?). Then there are plenty classes within the Jersey 2 dependency tree that suggest GZip encoding:

  • 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

I found that people on the web suggest using any of them even though I like to think that org.glassfish.jersey seems to be the right choice since it is an actual Jersey dependency. Not to speak of those that are found in the ApacheConnector related libraries. I have no idea which one I should actually use.

like image 525
Rafael Winterhalter Avatar asked Nov 04 '13 10:11

Rafael Winterhalter


People also ask

How do I enable gzip compression?

Gzip on Windows Servers (IIS Manager)Open up IIS Manager. Click on the site you want to enable compression for. Click on Compression (under IIS) Now Enable static compression and you are done!

How do I compress a body request?

To compress the HTTP request body, you must attach the HTTP header indicating the sending of HTTP request body compressed in gzip format while sending the request message from the Web Service client. Implement the processing for attaching the HTTP header in the client application.

How do I know if a response is gzip?

Double click on the file and select headers. Under 'Response headers' you are looking for the 'Connection-Encoding' field, it will say gzip if it is enabled.

How does gzip compression work?

GZIP compression is a data-compressing process through which the size of a file is reduced before it is transferred from the server to the browser. So, a GZIP compressed file is smaller in size when compared to the original, thus the browser renders its contents faster.


1 Answers

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.

like image 71
Rafael Winterhalter Avatar answered Oct 27 '22 14:10

Rafael Winterhalter