Uploaded image for project: 'RESTEasy'
  1. RESTEasy
  2. RESTEASY-1170

Unable to handle gzip Content-Encoding in the application

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Obsolete
    • Icon: Major Major
    • None
    • 3.0.10.Final
    • jaxrs
    • None

      I'm developing a small RESTEasy application and I want it 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).

      Apparently, there's no way to share an already gzipped stream. There's the org.jboss.resteasy.annotations.GZIP annotation (http://docs.jboss.org/resteasy/docs/3.0.9.Final/userguide/html/gzip.html). Nevertheless, it doesn't work as expected (or at least as I would). So, I have patched the class to this:

      package org.jboss.resteasy.plugins.interceptors.encoding;
      
      import java.io.IOException;
      import java.io.OutputStream;
      import java.lang.annotation.Annotation;
      import java.util.zip.GZIPOutputStream;
      
      import javax.annotation.Priority;
      import javax.ws.rs.Priorities;
      import javax.ws.rs.WebApplicationException;
      import javax.ws.rs.ext.Provider;
      import javax.ws.rs.ext.WriterInterceptor;
      import javax.ws.rs.ext.WriterInterceptorContext;
      
      import org.jboss.resteasy.annotations.GZIP;
      import org.jboss.resteasy.util.CommitHeaderOutputStream;
      
      /**
       * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
       * @version $Revision: 1 $
       */
      @Provider
      @Priority(Priorities.ENTITY_CODER)
      public class GZIPEncodingInterceptor implements WriterInterceptor {
      	public static class EndableGZIPOutputStream extends GZIPOutputStream {
      		public EndableGZIPOutputStream(OutputStream os) throws IOException {
      			super(os);
      		}
      
      		@Override
      		public void finish() throws IOException {
      			super.finish();
      			def.end(); // make sure on finish the deflater's end() is called to
      						// release the native code pointer
      		}
      	}
      
      	public static class CommittedGZIPOutputStream extends CommitHeaderOutputStream {
      		protected CommittedGZIPOutputStream(OutputStream delegate, CommitCallback headers) {
      			super(delegate, headers);
      		}
      
      		protected GZIPOutputStream gzip;
      
      		public GZIPOutputStream getGzip() {
      			return gzip;
      		}
      
      		@Override
      		public void commit() {
      			if (isHeadersCommitted)
      				return;
      			isHeadersCommitted = true;
      			try {
      				// GZIPOutputStream constructor writes to underlying OS causing
      				// headers to be written.
      				// so we swap gzip OS in when we are ready to write.
      				gzip = new EndableGZIPOutputStream(delegate);
      				delegate = gzip;
      			} catch (IOException e) {
      				throw new RuntimeException(e);
      			}
      		}
      	}
      
      	@Override
      	public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
      
      		// Original code decides by inspecting the Content-Encoding header right here.
      		boolean isGZIPAnnotated = false;
      		for (Annotation annotation : context.getAnnotations())
      			if (annotation instanceof GZIP) {
      				isGZIPAnnotated = true;
      				break;
      			}
      
      		if (isGZIPAnnotated) {
      
      			OutputStream old = context.getOutputStream();
      			// GZIPOutputStream constructor writes to underlying OS causing
      			// headers to be written.
      			CommittedGZIPOutputStream gzipOutputStream = new CommittedGZIPOutputStream(old, null);
      
      			// Any content length set will be obsolete
      			context.getHeaders().remove("Content-Length");
      			context.getHeaders().add("Content-Encoding", "gzip");
      
      			context.setOutputStream(gzipOutputStream);
      			try {
      				context.proceed();
      			} finally {
      				if (gzipOutputStream.getGzip() != null)
      					gzipOutputStream.getGzip().finish();
      				context.setOutputStream(old);
      			}
      			return;
      		} else {
      			context.proceed();
      		}
      	}
      }
      

      (I'm Sorry I didn't brought a patch) which basically examines if the annotation is present instead of reading the Content-Encoding header.

      With this I have successfully handle these three alternative combinations of the RequestBuilder.encoding method and the annotation:

      • No annotation and no method invocation: correct. Regular request.
      • No annotation and method invocation, returning gzipped content: ok. What I was unable to do before.
      • Annotation and no method invocation: correct. The (I think) expected behavior for the GZIP annotation.
      • Other combinations would be incorrect, I suppose.

            rsigal@redhat.com Ronald Sigal
            mschonaker Martín Schonaker (Inactive)
            Votes:
            2 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated:
              Resolved: