Wednesday, May 30, 2012

GZip Content-Encoding and and Chunked Transfer-Encoding with Liferay 6.0.x

Liferay has a GZipFilter which is turn on by default and gzip response for performance reason. Performance as in bandwidth saving and shorter download time for slow clients eg. those on dial-ups etc. This doesn't necessary means more CPU efficient, as gziping and related processing takes up CPU cycles.

GZipFilter is basically a javax.servlet.Filter that wraps up the HttpServletResponse with GZipResponse

  protected void processFilter(HttpServletRequest request, 
                               HttpServletResponse response, 
                               FilterChain filterChain) {
     ...
     if (isCompress(request) && !isInclude(request) &&
         BrowserSnifferUtil.acceptsGzip(request) &&
         !isAlreadyFiltered(request)) {

         ...
         GZipResponse gZipResponse = new GZipResponse(response);
         processFilter(GZipFilter.class, request, gZipResponse, filterChain);
         gZipResponse.finishResponse();
         ...
     }
     ...
  }

GZipResponse is just a wrapper that overrides methods related to OuputStream and Writer such that it delegates the output streaming and writing to GZipStream (at least for Liferay 6.0.x it is)

    public class GZipResponse extends HttpServletResponseWrapper {

            public GZipResponse(HttpServletResponse response) {
                    super(response);
    
                    _response = response;
            }
    
            public void finishResponse() {
                    try {
                            if (_writer != null) {
                                    _writer.close();
                            }
                            else if (_stream != null) {
                                    _stream.close();
                            }
                    }
                    catch (IOException e) {
                    }
            }
    
            public void flushBuffer() throws IOException {
                    if (_stream != null) {
                            _stream.flush();
                    }
            }
    
            public ServletOutputStream getOutputStream() throws IOException {
                    if (_writer != null) {
                            throw new IllegalStateException();
                    }
    
                    if (_stream == null) {
                            _stream = _createOutputStream();
                    }
   
                    return _stream;
            }
    
            public PrintWriter getWriter() throws IOException {
                    if (_writer != null) {
                            return _writer;
                    }
    
                    if (_stream != null) {
                            throw new IllegalStateException();
                    }
    
                    _stream = _createOutputStream();
    
                    _writer = new UnsyncPrintWriter(new OutputStreamWriter(
                            //_stream, _res.getCharacterEncoding()));
                            _stream, StringPool.UTF8));
    
                    return _writer;
            }
   
            private ServletOutputStream _createOutputStream() throws IOException {
                    return new GZipStream(_response);
            }

GZipStream upon close() just decides to write the content-length header to the response.

   public class GZipStream extends ServletOutputStream {
               public void close() throws IOException {
                    if (_closed) {
                            throw new IOException();
                    }
    
                    _gzipOutputStream.finish();
    
                    int contentLength = _unsyncByteArrayOutputStream.size();
    
                    _response.setContentLength(contentLength);
                    _response.addHeader(HttpHeaders.CONTENT_ENCODING, _GZIP);
    
                    try {
                            flushOutToOutputStream();
                    }
                    catch (IllegalStateException ise) {
                            flushOutToWriter();
                    }
    
                    _closed = true;
            }
   }
What this means is that if you want Content-Encoding to be 'gzip' and Transfer-Encoding to be 'chunked' in Liferay 6.0.x, the answer is not possible, unless you disabled GZipFilter through portal-ext.properties, and cooked up one of your own. Might want to consider Tomcat's connector which does 'gzip' content encoding as well as auto transfer-encoding as chunk when the buffer size is exceeded, if i got my facts right. It's a bit disappointing as I was hoping Liferay to handle this better. Chunked transfer-encoding is good when you have a huge content where you don't want to buffer it up cause it use up too much unnecessary memory.