17.6 Compression

Most popular browsers accept GZIP-compressed pages, decompressing them before displaying them to the user without the user knowing that the page was compressed. Google uses this technique to good effect. If the page size is largeor more precisely, the compressed page compared to the uncompressed page is so much smaller that the download time is consistently measurably reducedthen compressing pages is a worthwhile option. To fully determine the potential benefit of compressing pages, you also need to factor in the extra CPU load and time on the server to compress the file (and the extra time to decompress the file on the client, though you can usually ignore this if the download time is significantly improved). In practice, there is a heavier load on the server, but a significantly faster download for limited bandwidth clients.

The mechanics of the HTTP support for compressed pages follows. First, the browser tells the server that it can handle GZIP-compressed pages by including the header Accept-Encoding: gzip in the page request. The server can sense that the browser accepts GZIP compression quite easily by looking for the header with code like this:

public boolean acceptsGZIP(HttpServletRequest request) {
  //Get any "Accept-Encoding" headers
  String header;
  Enumeration e = ((HttpServletRequest)request).getHeaders(
                                "Accept-Encoding");
  while (e.hasMoreElements(  ))
  {
    String header = (String)e.nextElement(  );
    //And check that GZIP is supported
    if ( (header != null) && (header.toUpperCase(  ).indexOf("GZIP") > -1) )
      return true;
  }
  return false;
}

Next, if you are going to return the page compressed, your server needs to inform the browser that the page sent to it is compressed. Doing so requires a header to be set in the response:

public void setGZIPContent(HttpServletResponse response) {
  response.setHeader("Content-Encoding", "gzip");
}

Finally, the SDK directly supports GZIP compression in the java.util.zip package. Furthermore, since the GZIPOutputStream is a type of FilterOutputStream, you wrap the servlet output stream and write the data as you normally would:

  //Write output. First get the output stream
  OutputStream out;
  if (acceptsGZIP(request))
  {
    setGZIPContent(response);
    out = new GZIPOutputStream(response.getOutputStream(  ));
  }
  else
  {
    out = response.getOutputStream(  );
  }
  //Now write the page
  ...

This process is simplified here. In practice, you shouldn't waste time compressing small pages, since there is no gain in network transfer time, so you should test to see if the page is big enough to warrant compression.

You could also use a servlet filter instead of building compression support directly into the servlet. In this case, the filter would wrap the HttpServletResponse object with its own wrapper before passing the wrapped response object down the chain. When the servlet requests the output stream, the wrapped HttpServletResponse object then provides a GZIP-compressed output stream that wraps the original output stream. The effect is the same as the code shown earlier. Sun has contributed a servlet compression filter to the examples supplied with the Tomcat application server, and a JavaWorld article [4] by Jason Hunter describes the filter in more detail.

[4] Jason Hunter, "Filter code with Servlet 2.3 model," JavaWorld, June 2001, http://www.javaworld.com/javaworld/jw-06-2001/jw-0622-filters.html.

If you can cache the compressed version of the page the first time you write it, or statically compress the page prior to starting the servlet, then you gain the benefits of compression with none of the overhead. Servlet filters add overhead to servlet processing, so the nonfilter solution is slightly more efficient. However, the filter solution is probably much easier to add to existing deployments.