Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Filter code with Servlet 2.3 model

Discover freely available servlet filters you can use today

  • Print
  • Feedback

Page 4 of 5

Squeeze the response

The third filter on our menu today automatically compresses the response output stream, improving bandwidth utilization and providing a great demonstration of response object wrapping. This filter is based on one written by Amy Roh from Sun who contributed it to the Tomcat 4.0 "examples" Web application. You'll find the original code under webapps/examples/WEB-INF/classes/compressionFilters. That filter is available under the standard Apache license. The example code shown here and in the WAR has been edited for clarity and simplicity.

The strategy of the CompressionFilter class is to examine the request headers to determine if the client supports compression, and if so, wrap the response object with a custom response whose getOutputStream() and getWriter()methods have been customized to utilize a compressed output stream. Using filters allows such a simple yet powerful solution.

Looking at the code, we'll start with the filter's init()method:

public void init(FilterConfig filterConfig) {
    config = filterConfig;
    compressionThreshold = 0;
    if (filterConfig != null) {
      String str = filterConfig.getInitParameter("compressionThreshold");
      if (str != null) {
        compressionThreshold = Integer.parseInt(str);
      }
      else {
        compressionThreshold = 0;
      }
    }
  }


When called before the filter is put into service, this init()method looks for the presence of a filter initparameter to determine the compression threshold -- the amount of bytes that must be in the response before it's worth compressing.

The doFilter() method, called when the request comes in, retrieves the Accept-Encoding header, and if the header value includes gzip, wraps the response and sets the threshold on the wrapper:

public void doFilter(ServletRequest request, ServletResponse response,
                    FilterChain chain ) throws IOException, ServletException {
    boolean supportCompression = false;
    if (request instanceof HttpServletRequest) {
      Enumeration e = ((HttpServletRequest)request)
                           .getHeaders("Accept-Encoding");
      while (e.hasMoreElements()) {
        String name = (String)e.nextElement();
        if (name.indexOf("gzip") != -1) {
          supportCompression = true;
        }
      }
    }
    if (!supportCompression) {
      chain.doFilter(request, response);
    }
    else {
      if (response instanceof HttpServletResponse) {
        CompressionResponseWrapper wrappedResponse = 
          new CompressionResponseWrapper((HttpServletResponse)response);
        wrappedResponse.setCompressionThreshold(compressionThreshold);
        chain.doFilter(request, wrappedResponse);
      }
    }
  }


Notice how the request must be cast to an HttpServletRequestbefore retrieving headers, just as with the first example. The filter uses the wrapper class CompressionResponseWrapper, a custom class extending the standard HttpServletResponseWrapper. The code for the wrapper is relatively simple:

public class CompressionResponseWrapper extends HttpServletResponseWrapper {
  protected ServletOutputStream stream = null;
  protected PrintWriter writer = null;
  protected int threshold = 0;
  protected HttpServletResponse origResponse = null;
  public CompressionResponseWrapper(HttpServletResponse response) {
    super(response);
    origResponse = response;
  }
  public void setCompressionThreshold(int threshold) {
    this.threshold = threshold;
  }
  public ServletOutputStream createOutputStream() throws IOException {
    return (new CompressionResponseStream(origResponse));
  }
  public ServletOutputStream getOutputStream() throws IOException {
    if (writer != null) {
      throw new IllegalStateException("getWriter() has already been " +
                                      "called for this response");
    }
    if (stream == null) {
      stream = createOutputStream();
    }
    ((CompressionResponseStream) stream).setCommit(true);
    ((CompressionResponseStream) stream).setBuffer(threshold);
    return stream;
  }
  public PrintWriter getWriter() throws IOException {
    if (writer != null) {
      return writer;
    }
    if (stream != null) {
      throw new IllegalStateException("getOutputStream() has already " +
                                      "been called for this response");
    }
    stream = createOutputStream();
    ((CompressionResponseStream) stream).setCommit(true);
    ((CompressionResponseStream) stream).setBuffer(threshold);
    writer = new PrintWriter(stream);
    return writer;
  }
}


Any call to getOutputStream() or getWriter()returns an object using CompressResponseStreamunder the covers. The CompressionResponseStreamclass isn't shown in this example, but it extends ServletOutputStream and compresses the stream using the java.util.zip.GZIPOutputStreamclass.

The Tomcat "examples" Web application comes preconfigured with the compression filter enabled with an example servlet set up. The example servlet responds to the /CompressionTest URL (make sure to prepend the /examples context path). With the WAR file I've made available, you can access the test servlet at /servlet/compressionTest(again, remember to prepend the appropriate context path). You can use the following web.xmlsnippet to configure the test:

<
filter>
    <filter-name>compressionFilter</filter-name>
    <filter-class>CompressionFilter</filter-class>
    <init-param>
      <param-name>compressionThreshold</param-name>
      <param-value>10</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>compressionFilter</filter-name>
    <servlet-name>compressionTest</servlet-name>
</filter-mapping>
<servlet>
  <servlet-name>
    compressionTest
  </servlet-name>
  <servlet-class>
    CompressionTestServlet
  </servlet-class>
</servlet>


The CompressionTestServlet (not shown here) prints whether or not the compression was possible, and when possible, announces the success with a compressed response!

File upload filter

The final filter we'll look at handles multipart/form-data POSTrequests, the type of request that can contain file uploads. Each multipart/form-data POST request contains any number of parameters and files, using a special format not natively understood by servlets. Servlet developers have historically used third-party classes to handle the uploads, such as the MultipartRequest and MultipartParserclasses found in my own com.oreilly.servletpackage. Here we see a new approach using a MultipartFilter to make the handling of such requests easier. The filter builds on the parsers in the com.oreilly.servletpackage and has been integrated into the package. (See Resources.)

The MultipartFilter works by watching incoming requests and when it detects a file upload request (with the content type multipart/form-data), the filter wraps the request object with a special request wrapper that knows how to parse the special content type format. A servlet receiving the special request wrapper has seamless access to the multipart parameters through the standard getParameter() methods, because the wrapper has redefined the behavior of those methods. The servlet can also handle uploaded files by casting the request to the wrapper type and using the additional getFile()methods on the wrapper.

Here is the code for the filter:

package com.oreilly.servlet;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class MultipartFilter implements Filter {
  private FilterConfig config = null;
  private String dir = null;
  public void init(FilterConfig config) throws ServletException {
    this.config = config;
    // Determine the upload directory.  First look for an uploadDir filter
    // init parameter.  Then look for the context tempdir.
    dir = config.getInitParameter("uploadDir");
    if (dir == null) {
      File tempdir = (File) config.getServletContext()
                  .getAttribute("javax.servlet.context.tempdir");
      if (tempdir != null) {
        dir = tempdir.toString();
      }
      else {
        throw new ServletException(
          "MultipartFilter: No upload directory found: set an uploadDir " +
          "init parameter or ensure the javax.servlet.context.tempdir " +
          "directory is valid");
      }
    }
  }
  public void destroy() {
    config = null;
  }
  public void doFilter(ServletRequest request, ServletResponse response,
                     FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    String type = req.getHeader("Content-Type");
    // If this is not a multipart/form-data request continue
    if (type == null || !type.startsWith("multipart/form-data")) {
      chain.doFilter(request, response);
    }
    else {
      MultipartWrapper multi = new MultipartWrapper(req, dir);
      chain.doFilter(multi, response);
    }
  }
}


The init() method determines the file upload directory. This is the location where the multipart parser places files, so that the entire request doesn't have to reside in memory. It looks first for an uploadDir filter initparameter, and failing to find that, defaults to the tempdir directory -- a standard context attribute added in Servlet API 2.2.

  • Print
  • Feedback

Resources