2

I'm developing a small JAX-RS application with Resteasy. I wanted the application to serve some static content for Javascript and CSS files, etc. and I would like to take advantage of the already gzipped version of the resources packaged in the jars of webjars.org. Thus, I need to handle the Accept-Encoding header and check if the .gz is there (or not).

So far, what I have is:

@Path("res/{path:.*}")
@GET
public Response webjars(@PathParam("path") String path, @HeaderParam("Accept-Encoding") String acceptEncoding) {

    // Guesses MIME type from the path extension elsewhere.
    String mime = mimes.getContentType(path);

    if (acceptEncoding.contains("gzip")) {
        InputStream is = getClass().getResourceAsStream("/META-INF/resources/webjars/" + path + ".gz");
        if (is != null)
            return Response.ok().type(mime).encoding("gzip").entity(is).build();
    }

    InputStream is = getClass().getResourceAsStream("/META-INF/resources/webjars/" + path);
    if (is != null)
        return Response.ok().type(mime).entity(is).build();

    return Response.status(Status.NOT_FOUND).build();
}

But it doesn't work. The content served is totally broken. So far, I've found that a component that compresses the stream again: org.jboss.resteasy.plugins.interceptors.encoding.GZIPEncodingInterceptor because I manually filled the Content-Encoding header (using the ResponseBuilder.encoding method).

This looks like a bug to me because, apparently, there's no way to share an already gzipped stream. However, Is this achievable using JAX-RS? Is this a Resteasy bug?

I can think of a variety of ways to achieve the same thing externally to Resteasy, like mapping the webjars.org servlet (I'm not in a Servlet API 3.0 environment, so I have no META-INF/resources/ automatic classpath mapping). Nevertheless, my questions still prevail. It applies to several other scenarios.

Update:

For the record I have filled the issue RESTEASY-1170.

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
Martín Schonaker
  • 7,273
  • 4
  • 32
  • 55
  • [It's not a bug; it's documented](http://docs.jboss.org/resteasy/docs/3.0.9.Final/userguide/html/gzip.html). I'm thinking to avoid disabling the entire feature (which I'm not exactly sure how to), you could simple implement your own interceptor and name bind it. The problem is with the Content-Encoding header. Your custom interceptor would simply add the header. That's the only thing I can think of. Unless you want to completely disable the feature. For that you'll need to do some research :-) – Paul Samsotha Apr 15 '15 at 15:38
  • Yes, I know about the annotation. Shouldn't be the GZIP annotation enough to intentionally enable the compression? Why inspecting the `Content-Encoding` header if so? – Martín Schonaker Apr 15 '15 at 15:44
  • 1
    I didn't mention anything about the `@GZIP` annotation. The point I'm getting at is that if you don't want it to be handle by the current interceptor, don't set the header, create an Interceptor that will be name binded, with your own annotation, and set the priority to one lower than the one you want to avoid, then set the header in your Interceptor. Seems like a lot (it really isn't), but it's the only thing I can think of right now. – Paul Samsotha Apr 15 '15 at 15:49
  • I'll add that solution to my list of "ways to achieve the same thing" I mentioned. Still seems like a bug to me. – Martín Schonaker Apr 15 '15 at 15:53
  • limitation != bug. You can always file and issue – Paul Samsotha Apr 15 '15 at 15:54

1 Answers1

4

Here's an example implementation of my above comment.

The point I'm getting at is that if you don't want it to be handle by the current interceptor, don't set the header, create an Interceptor that will be name binded, with your own annotation, and set the priority to one lower than the one you want to avoid, then set the header in your Interceptor...

@AlreadyGzipped

@NameBinding
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AlreadyGzipped {}

WriterInterceptor. Notice the @Priority. The GZIPEncodingInterceptor uses Priorities.ENTITY_CODER

@Provider
@AlreadyGzipped
@Priority(Priorities.ENTITY_CODER + 1000)
public class AlreadyGzippedWriterInterceptor implements WriterInterceptor {
    @Context HttpHeaders headers;

    @Override
    public void aroundWriteTo(WriterInterceptorContext wic) throws IOException, 
                                                      WebApplicationException {
        String header = headers.getHeaderString("Accept-Encoding");
        if (null != header && header.equalsIgnoreCase("gzip")) {
            wic.getHeaders().putSingle("Content-Encoding", "gzip");
        }
        wic.proceed();
    }  
}

Test resource

@Path("resource")
public class AlreadyGzippedResoure {

    @GET
    @AlreadyGzipped
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    public Response getAlreadGzipped() throws Exception {
        InputStream is = getClass().getResourceAsStream("/stackoverflow.png.gz");
        return Response.ok(is).build();
    }
}

Test

public class Main {
    public static void main(String[] args) throws Exception {
        Client client = ClientBuilder.newClient();
        String url = "http://localhost:8080/api/resource";

        Response response = client.target(url).request().acceptEncoding("gzip").get();
        Image image = ImageIO.read(response.readEntity(InputStream.class));
        JOptionPane.showMessageDialog(null,new JLabel(new ImageIcon(image)));
    }
}

Result

enter image description here

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • Detailed answer! I should add though, remember to register the interceptor else it won't be called. How to register it will depend on how your app is set up, see examples here - https://stackoverflow.com/questions/19785001/custom-method-annotation-using-jerseys-abstracthttpcontextinjectable-not-workin/20611842#20611842 – papigee Feb 24 '20 at 16:16
  • 1
    Thank you for this answer. I noticed that many clients (e.g. Chrome) list several possible encodings (e.g. "gzip, deflate, br"), so it should probably be a contains rather than an equals. – Florian Enner Jul 09 '20 at 12:44