JavaBlog.fr / Java.lu DEVELOPMENT,Java,WEB Java/Web: GZIP compression with Http Filter, GZIPFilter, GZIPResponseStream, GZIPResponseWrapper

Java/Web: GZIP compression with Http Filter, GZIPFilter, GZIPResponseStream, GZIPResponseWrapper

Hi,

The bandwidth is an important concern of the production management of a website. A recognized method of all browsers and systems to drastically reduce the bandwidth usage is the Zip compression of data sent on the fly, decompressed without loss and displayed by the browser. In this article, we will discuss about the configuration of gzip compression in the client-server exchanges of Web based application. The protocol used is called GZIP.

Side browsers, most (99%) browsers can decode gzipped content. The header of such content is ‘Content-Encoding: gzip’, and is accepted by all browsers support HTTP 1.1, so ‘Content-Encoding’. Side of the web servers, the existence or installation of gzip module on a webserver does not necessarily lead to data compression, because it compresses the data only if the request from the browser contains the header ‘Accept-Encoding: gzip, deflate’.

Configuration Http filter and classes
The web deployment descriptor web.xml must contains the configuration of http filter GZIPFilter:

<filter>
	<filter-name>GZIPFilter</filter-name>
	<filter-class>com.ho.filter.GZIPFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>GZIPFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

When a request arrived from browser, on server side, the http filter GZIPFilter will check the need of GZIP compression in request’s headers ‘Accept-Encoding: gzip’. Then, this filter uses two classes GZIPResponseWrapper and GZIPResponseStream to compress the data in response:

GZIPFilter:

package com.ho.filter;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class GZIPFilter implements Filter {

	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
		throws IOException, ServletException {
		if (req instanceof HttpServletRequest) {
			HttpServletRequest request = (HttpServletRequest) req;
			HttpServletResponse response = (HttpServletResponse) res;
			String ae = request.getHeader("accept-encoding");
			if (ae != null && ae.indexOf("gzip") != -1) {
				GZIPResponseWrapper wrappedResponse = new GZIPResponseWrapper(response);
				chain.doFilter(req, wrappedResponse);
				wrappedResponse.finishResponse();
				return;
			}
			chain.doFilter(req, res);
		}
	}

	public void init(FilterConfig filterConfig) {
		// noop
	}

	public void destroy() {
		// noop
	}
}

GZIPResponseWrapper:

public class GZIPResponseWrapper extends HttpServletResponseWrapper {
  protected HttpServletResponse origResponse = null;
  protected ServletOutputStream stream = null;
  protected PrintWriter writer = null;

  public GZIPResponseWrapper(HttpServletResponse response) {
    super(response);
    origResponse = response;
  }

  public ServletOutputStream createOutputStream() throws IOException {
    return (new GZIPResponseStream(origResponse));
  }

  public void finishResponse() {
    try {
      if (writer != null) {
        writer.close();
      } else {
        if (stream != null) {
          stream.close();
        }
      }
    } catch (IOException e) {}
  }

  public void flushBuffer() throws IOException {
    stream.flush();
  }

  public ServletOutputStream getOutputStream() throws IOException {
    if (writer != null) {
      throw new IllegalStateException("getWriter() has already been called!");
    }

    if (stream == null)
      stream = createOutputStream();
    return (stream);
  }

  public PrintWriter getWriter() throws IOException {
    if (writer != null) {
      return (writer);
    }

    if (stream != null) {
      throw new IllegalStateException("getOutputStream() has already been called!");
    }

   stream = createOutputStream();
   writer = new PrintWriter(new OutputStreamWriter(stream, "UTF-8"));
   return (writer);
  }

  public void setContentLength(int length) {}
}

GZIPResponseStream:

public class GZIPResponseStream extends ServletOutputStream {
  protected ByteArrayOutputStream baos = null;
  protected GZIPOutputStream gzipstream = null;
  protected boolean closed = false;
  protected HttpServletResponse response = null;
  protected ServletOutputStream output = null;

  public GZIPResponseStream(HttpServletResponse response) throws IOException {
    super();
    closed = false;
    this.response = response;
    this.output = response.getOutputStream();
    baos = new ByteArrayOutputStream();
    gzipstream = new GZIPOutputStream(baos);
  }

  public void close() throws IOException {
    if (closed) {
      throw new IOException("This output stream has already been closed");
    }
    gzipstream.finish();

    byte[] bytes = baos.toByteArray();


    response.addHeader("Content-Length", 
                       Integer.toString(bytes.length)); 
    response.addHeader("Content-Encoding", "gzip");
    output.write(bytes);
    output.flush();
    output.close();
    closed = true;
  }

  public void flush() throws IOException {
    if (closed) {
      throw new IOException("Cannot flush a closed output stream");
    }
    gzipstream.flush();
  }

  public void write(int b) throws IOException {
    if (closed) {
      throw new IOException("Cannot write to a closed output stream");
    }
    gzipstream.write((byte)b);
  }

  public void write(byte b[]) throws IOException {
    write(b, 0, b.length);
  }

  public void write(byte b[], int off, int len) throws IOException {
    if (closed) {
      throw new IOException("Cannot write to a closed output stream");
    }
    gzipstream.write(b, off, len);
  }

  public boolean closed() {
    return (this.closed);
  }
  
  public void reset() {
    //noop
  }
}

These classes are present in the attachment or on the www.java2s.com site.

Tests
To verify the compression, we could the TCP/IP Monitor view of Eclipse. So, a proxy will be configured in order to analyze the request coming to the server localhost on port 9999, then forward these requests to this same server localhost on port 8080.

…without GZIP configuration:
– the size of response is 27560 bytes,
– the Content-Type of response is text/html; ,
– the header Content-Encoding is missing in response,

…with GZIP configuration:
– the size of response is 2544 bytes,
– the Content-Type of response is text/html; ,
– the header Content-Encoding contains the value gzip in response,

That’s all!!!

Download: GZIPFilter.zip

Best regards,

Huseyin OZVEREN

5 thoughts on “Java/Web: GZIP compression with Http Filter, GZIPFilter, GZIPResponseStream, GZIPResponseWrapper”

    1. Indeed, you must call the gzipstream.close() method in GZIPResponseStream.close() method if the application has only a single HTTP filter. However, if the application has several HTTP filters, do not call the gzipstream.close() method …

      Note: the javadoc for this GZIPOutputStream.finish() method is:
      Finishes writing compressed data to the output stream without closing the underlying stream. Use this method when applying multiple filters in succession to the same output stream.

  1. How to handle exceptions in filters , incase filter did not receive proper gzip data . ? Please let me know if any other information needed.

Leave a Reply

Your email address will not be published.

Time limit is exhausted. Please reload CAPTCHA.

Related Post