Servlets.com

Home

What's New?

com.oreilly.servlet

Servlet Polls

Mailing Lists

Servlet Engines

Servlet ISPs

Servlet Tools

Documentation

Online Articles

The Soapbox

"Java Servlet
Programming,
Second Edition"

"Java Enterprise
Best Practices"

Speaking & Slides

About Jason

XQuery Affiliate

Filter code with Servlet 2.3
Discover freely available servlet filters you can use today

(Originally published in JavaWorld, June 2001)

Summary
Jason Hunter looks in-depth at the new servlet filter model with an examination of several freely available filters. You'll learn how these filters work and what you can do with them. For a grand finale, Jason describes his own multipart request filter that simplifies the handling of file uploads. (3,200 words)

By Jason Hunter

In "Servlet 2.3: New Features Exposed," I introduced the changes coming in the Servlet API 2.3 and gave a short tutorial on the new servlet filter model. In this follow-on article, I'll dig deeper into servlet filters and look at several filters you can download for free on the Web. For each filter, I'll examine what it does, how it works, and where you can get it.

You can use this article in two ways: to learn about some filters that are useful out of the box, or as an aid in writing your own filters. I'll start off with some simple examples and then move on to more advanced ones. At the end, I'll show you a file upload filter that I wrote to support multipart requests.

Servlet filters
In case you aren't yet familiar, a filter is an object that can transform a request or modify a response. Filters are not servlets; they don't actually create a response. They are preprocessors of the request before it reaches a servlet, and/or postprocessors of the response leaving a servlet. As you'll see later in the examples, a filter can:

  • Intercept a servlet's invocation before the servlet is called
  • Examine a request before a servlet is called
  • Modify the request headers and request data by providing a customized version of the request object that wraps the real request
  • Modify the response headers and response data by providing a customized version of the response object that wraps the real response
  • Intercept a servlet's invocation after the servlet is called
You can configure a filter to act on a servlet or group of servlets. Zero or more filters can filter one or more servlets. A filter implements javax.servlet.Filter and defines its three methods:
  1. void init(FilterConfig config) throws ServletException: Called before the filter goes into service, and sets the filter's configuration object
  2. void destroy(): Called after the filter has been taken out of service
  3. void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException: Performs the actual filtering work
The server calls init(FilterConfig) once to prepare the filter for service, then calls doFilter() any number of times for requests specially set up to use the filter. The FilterConfig interface has methods to retrieve the filter's name, its init parameters, and the active servlet context. The server calls destroy() to indicate that the filter is being taken out of service. The filter lifecycle is now very similar to the servlet lifecycle -- a change recently made in the Servlet API 2.3 Public Final Draft #2. Previously the lifecycle involved a setFilterConfig(FilterConfig) method.

In its doFilter() method, each filter receives the current request and response, as well as a FilterChain containing the filters that still must be processed. In the doFilter() method, a filter may do what it wants with the request and response. (It could gather data by calling their methods, or wrap the objects to give them new behavior, as I'll discuss later.) The filter then calls chain.doFilter() to transfer control to the next filter. When that call returns, a filter can, at the end of its own doFilter() method, perform additional work on the response; for instance, it can log information about the response. If the filter wants to halt the request processing and gain full control of the response, it can intentionally not call the next filter.

Spot the slowdown
To really understand filters, you have to see them in action. The first filter we'll look at is simple but powerful; it records the duration of all requests. It's modeled loosely after the non-descriptively named ExampleFilter from the Tomcat 4.0 distribution. Here is the code:

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

public class TimerFilter implements Filter {

  private FilterConfig config = null;

  public void init(FilterConfig config) throws ServletException {
    this.config = config;
  }

  public void destroy() {
    config = null;
  }

  public void doFilter(ServletRequest request, ServletResponse response,
                     FilterChain chain) throws IOException, ServletException {
    long before = System.currentTimeMillis();
    chain.doFilter(request, response);
    long after = System.currentTimeMillis();

    String name = "";
    if (request instanceof HttpServletRequest) {
      name = ((HttpServletRequest)request).getRequestURI();
    }
    config.getServletContext().log(name + ": " + (after - before) + "ms");
  }
}
When the server calls init(), the filter saves a reference to the config in its config variable, which is later used in the doFilter() method to retrieve the ServletContext. When the server calls doFilter(), the filter times how long the request handling takes and logs the time once processing has completed. This filter nicely demonstrates before- and after-request processing. Notice that the parameters to the doFilter() method are not HTTP-aware objects, so to call the HTTP-specific getRequestURI() method requires a cast of the request to an HttpServletRequest type.

To use this filter, you must declare it in the web.xml deployment descriptor using the <filter> tag, as shown below:

<filter>
    <filter-name>timerFilter</filter-name>
    <filter-class>TimerFilter</filter-class>
</filter>
This notifies the server that a filter named timerFilter is implemented in the TimerFilter class. You can apply a registered filter to certain URL patterns or servlet names using the <filter-mapping> tag:
<filter-mapping>
    <filter-name>timerFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
This configures the filter to operate on all requests to the web application (static or dynamic), just what we want for our timing filter. If you connect to a simple page, the log output might look like this:
2001-05-25 00:14:11 /timer/index.html: 10ms
In Tomcat 4.0 beta5, you'll find the log file under server_root/logs/.

Who's on your site, and what are they doing?
Our next filter is a clickstream filter written by the folks at OpenSymphony. This filter tracks user requests (a.k.a. clicks) and request sequences (a.k.a. clickstreams) to show a site administrator who's visiting her site and what pages each visitor has accessed so far. It's an open source library, under the LGPL license.

Inside the clickstream library you'll find a ClickstreamFilter class that captures request information, a Clickstream class that operates like a struct to hold data, and a ClickstreamLogger class that captures session and context events to glue everything together. There's also a BotChecker class that determines if a client is a robot (using simple logic, like "Did they request robots.txt?"). To view the data, the library provides a clickstreams.jsp visitor summary page and a supporting viewstream.jsp visitor detail page.

We'll look first at the ClickstreamFilter class. All these examples are slightly modified from the original, for formatting and to fix portability issues, which I'll discuss later.

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

public class ClickstreamFilter implements Filter {
  protected FilterConfig filterConfig;
  private final static String FILTER_APPLIED = "_clickstream_filter_applied";

  public void init(FilterConfig config) throws ServletException {
    this.filterConfig = filterConfig;
  }

  public void doFilter(ServletRequest request, ServletResponse response,
                   FilterChain chain) throws IOException, ServletException {
    // Ensure that filter is only applied once per request.
    if (request.getAttribute(FILTER_APPLIED) == null) {
      request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
      HttpSession session = ((HttpServletRequest)request).getSession();
      Clickstream stream = (Clickstream)session.getAttribute("clickstream");
      stream.addRequest(((HttpServletRequest)request));
    }

    // pass the request on
    chain.doFilter(request, response);
  }

  public void destroy() { }
}
The doFilter() method gets the user session, obtains the Clickstream from the session, and adds the current request data to the Clickstream. It uses a special FILTER_APPLIED marker attribute to note if the filter was already applied for this request (as might happen during request dispatching) and to ignore any follow-on filtering action. You might be wondering how the filter knows that the clickstream attribute will be present in the session. That's because the ClickstreamLogger places it there when the session is created. Here is the ClickstreamLogger code:
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class ClickstreamLogger implements ServletContextListener,
                                          HttpSessionListener {
  Map clickstreams = new HashMap();

  public ClickstreamLogger() { }

  public void contextInitialized(ServletContextEvent sce) {
    sce.getServletContext().setAttribute("clickstreams", clickstreams);
  }

  public void contextDestroyed(ServletContextEvent sce) {
    sce.getServletContext().setAttribute("clickstreams", null);
  }

  public void sessionCreated(HttpSessionEvent hse) {
    HttpSession session = hse.getSession();
    Clickstream clickstream = new Clickstream();
    session.setAttribute("clickstream", clickstream);
    clickstreams.put(session.getId(), clickstream);
  }

  public void sessionDestroyed(HttpSessionEvent hse) {
    HttpSession session = hse.getSession();
    Clickstream stream = (Clickstream)session.getAttribute("clickstream");
    clickstreams.remove(session.getId());
  }
}
The logger receives application events and uses them to bind everything together. On context creation, the logger places a shared map of streams into the context. This allows the clickstreams.jsp page to know what streams are currently active. On context destruction, the logger removes the map. When a new visitor creates a new session, the logger places a new Clickstream instance into the session and adds the Clickstream to the central map of streams. On session destruction, the logger removes the stream from the central map.

The following web.xml deployment descriptor snippet wires everything together:

    <filter>
         <filter-name>clickstreamFilter</filter-name>
         <filter-class>ClickstreamFilter</filter-class>
    </filter>

    <filter-mapping>
         <filter-name>clickstreamFilter</filter-name>
         <url-pattern>*.jsp</url-pattern>
    </filter-mapping>

    <filter-mapping>
         <filter-name>clickstreamFilter</filter-name>
         <url-pattern>*.html</url-pattern>
    </filter-mapping>

    <listener>
         <listener-class>ClickstreamLogger</listener-class>
    </listener>
This registers the ClickstreamFilter and sets it up to handle *.jsp and *.html requests. This also registers the ClickstreamLogger as a listener to receive application events when they occur.

The two JSP pages pull the clickstream data from the session and context objects and use an HTML interface to display the current status. The following clickstreams.jsp file shows the overall summary:

<%@ page import="java.util.*" %>
<%@ page import="Clickstream" %>
<%
Map clickstreams = (Map)application.getAttribute("clickstreams");
String showbots = "false";

if (request.getParameter("showbots") != null) {
  if (request.getParameter("showbots").equals("true"))
    showbots = "true";
  else if (request.getParameter("showbots").equals("both"))
    showbots = "both";
}
%>

<font face="Verdana" size="-1">
<h1>All Clickstreams</h1>

<a href="clickstreams.jsp?showbots=false">No Bots</a> |
<a href="clickstreams.jsp?showbots=true">All Bots</a> |
<a href="clickstreams.jsp?showbots=both">Both</a> <p>

<% if (clickstreams.keySet().size() == 0) { %>
        No clickstreams in progress
<% } %>

<%
Iterator it = clickstreams.keySet().iterator();
int count = 0;
while (it.hasNext()) {
  String key = (String)it.next();
  Clickstream stream = (Clickstream)clickstreams.get(key);

  if (showbots.equals("false") && stream.isBot()) {
    continue;
  }
  else if (showbots.equals("true") && !stream.isBot()) {
    continue;
  }
  count++;
  try {
%>

<%= count %>.
<a href="viewstream.jsp?sid=<%= key %>"><b>
<%= (stream.getHostname() != null && !stream.getHostname().equals("") ?
     stream.getHostname() : "Stream") %>
</b></a> <font size="-1">[<%= stream.getStream().size() %> reqs]</font><br>

<%
  }
  catch (Exception e) {
%>
  An error occurred - <%= e %><br>
<%
  }
}
%>
The package is fairly easy to download and install from the OpenSymphony website. Place and compile the Java files in WEB-INF/classes, put the JSP files in the Web application root, and modify the web.xml file as instructed. To save you the hassle of even this much work, you can find a prepackaged WAR file available in Resources.

For the filter to work on Tomcat 4.0 beta5, I found I had to make some slight portability modifications. The changes I made show some common pitfalls in servlet and filter portability, so I'll list them here:

  • I had to add an extra import line to the JSP files: <%@ page import="Clickstream" %>. In Java you don't have to import classes within your own package, so on servers where JSPs compile into the default package, you don't need an import line like this. But on servers like Tomcat where JSPs compile into a custom package, you have to explicitly import classes in the default package.
  • I had to move the <listener> element in the web.xml file after the <filter> and <filter-mapping> elements, as required by the deployment descriptor DTD. Not all servers require elements to be in the proper order, but Tomcat does.
  • I had to change the web.xml mapping from /*.html and /*.jsp to the more correct *.html and *.jsp. Some servers are forgiving of the leading slash, but Tomcat rigidly enforces the rule that prohibits that slash.
  • Finally, I brought the ClickstreamFilter class up to the latest lifecycle API, changing setFilterConfig() to the newer init() and destroy() methods.
The downloadable WAR contains all these modifications and should run out-of-the-box across servers, although I haven't tested it widely.

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 init parameter 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 HttpServletRequest before 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 CompressResponseStream under the covers. The CompressionResponseStream class isn't shown in this example, but it extends ServletOutputStream and compresses the stream using the java.util.zip.GZIPOutputStream class.

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.xml snippet 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 POST requests, 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 MultipartParser classes found in my own com.oreilly.servlet package. 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.servlet package 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 init parameter, and failing to find that, defaults to the tempdir directory -- a standard context attribute added in Servlet API 2.2.

The doFilter() method examines the request content type, and should it be a multipart/form-data request, wraps the request with a MultipartWrapper. The wrapper code looks like this:

package com.oreilly.servlet;

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

public class MultipartWrapper extends HttpServletRequestWrapper {

  MultipartRequest mreq = null;

  public MultipartWrapper(HttpServletRequest req, String dir)
                                     throws IOException {
    super(req);
    mreq = new MultipartRequest(req, dir);
  }

  // Methods to replace HSR methods
  public Enumeration getParameterNames() {
    return mreq.getParameterNames();
  }
  public String getParameter(String name) {
    return mreq.getParameter(name);
  }
  public String[] getParameterValues(String name) {
    return mreq.getParameterValues(name);
  }
  public Map getParameterMap() {
    Map map = new HashMap();
    Enumeration enum = getParameterNames();
    while (enum.hasMoreElements()) {
      String name = (String) enum.nextElement();
      map.put(name, mreq.getParameterValues(name));
    }
    return map;
  }

  // Methods only in MultipartRequest
  public Enumeration getFileNames() {
    return mreq.getFileNames();
  }
  public String getFilesystemName(String name) {
    return mreq.getFilesystemName(name);
  }
  public String getContentType(String name) {
    return mreq.getContentType(name);
  }
  public File getFile(String name) {
    return mreq.getFile(name);
  }
}
The wrapper constructs a com.oreilly.servlet.MultipartRequest object to handle the upload parsing and overrides the getParameter() family of methods to use the MultipartRequest rather than the raw request to read parameter values. The wrapper also defines various getFile() methods so that a servlet receiving this wrapped request can call additional methods to handle the uploaded files.

The web.xml deployment descriptor adds the filter like this:

<filter>
    <filter-name>multipartFilter</filter-name>
    <filter-class>com.oreilly.servlet.MultipartFilter</filter-class>
    <!--
    <init-param>
      <param-name>uploadDir</param-name>
      <param-value>/tmp</param-value>
    </init-param>
    -->
</filter>

<filter-mapping>
    <filter-name>multipartFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<servlet>
    <servlet-name>
        uploadTest
    </servlet-name>
    <servlet-class>
        UploadTest
    </servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>
        uploadTest
    </servlet-name>
    <url-pattern>
        /uploadTest
    </url-pattern>
</servlet-mapping>
The UploadTest servlet looks like this:
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

import com.oreilly.servlet.*;

public class UploadTest extends HttpServlet {

  public void doPost(HttpServletRequest req, HttpServletResponse res)
                                throws ServletException, IOException {
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();

    out.println("<HTML>");
    out.println("<HEAD><TITLE>UploadTest</TITLE></HEAD>");
    out.println("<BODY>");
    out.println("<H1>UploadTest</H1>");

    // Parameters can now be read the same way for both
    // application/x-www-form-urlencoded and multipart/form-data requests!

    out.println("<H3>Request Parameters:</H3><PRE>");
    Enumeration enum = req.getParameterNames();
    while (enum.hasMoreElements()) {
      String name = (String) enum.nextElement();
      String values[] = req.getParameterValues(name);
      if (values != null) {
        for (int i = 0; i < values.length; i++) {
          out.println(name + " (" + i + "): " + values[i]);
        }
      }
    }
    out.println("</PRE>");

    // Files can be read if the request class is MultipartWrapper
    // Init params to MultipartWrapper control the upload handling

    if (req instanceof MultipartWrapper) {
      try {
        // Cast the request to a MultipartWrapper
        MultipartWrapper multi = (MultipartWrapper) req;

        // Show which files we received
        out.println("<H3>Files:</H3>");
        out.println("<PRE>");
        Enumeration files = multi.getFileNames();
        while (files.hasMoreElements()) {
          String name = (String)files.nextElement();
          String filename = multi.getFilesystemName(name);
          String type = multi.getContentType(name);
          File f = multi.getFile(name);
          out.println("name: " + name);
          out.println("filename: " + filename);
          out.println("type: " + type);
          if (f != null) {
            out.println("length: " + f.length());
          }
          out.println();
        }
        out.println("</PRE>");
      }
      catch (Exception e) {
        out.println("<PRE>");
        e.printStackTrace(out);
        out.println("</PRE>");
      }
    }

    out.println("</BODY></HTML>");
  }
}
The first half of the servlet shows how the filter exposes parameter data without any change to the receiving servlet. The second half shows how a servlet can downcase the request to a MultipartWrapper in order to expose the additional file-access methods.

An example HTML form to drive this servlet is shown here:

<FORM ACTION="uploadTest" ENCTYPE="multipart/form-data" METHOD=POST>
What is your name? <INPUT TYPE=TEXT NAME=submitter> <BR>
What is your age? <INPUT TYPE=TEXT NAME=age> <BR>
Which file do you want to upload? <INPUT TYPE=FILE NAME=file1>
<BR>
Any other file to upload? <INPUT TYPE=FILE NAME=file2> <BR>
<INPUT TYPE=SUBMIT>
</FORM>
Here's some possible output:
UploadTest
Request Parameters:
submitter (0): Jason
age (0): 28

Files:
name: file1
filename: 4008b21.tif
type: application/octet-stream
length: 39396

name: file2
filename: null
type: null
Some of you may be wondering what assurance we have that the MultipartWrapper set by the filter will be directly exposed to the follow-on servlet. In the specification Public Draft #2 and in Tomcat 4.0 beta5, there is no guarantee. In fact, if you try to access the servlet as /servlet/UploadTest, you'll notice the filtering doesn't work quite right, because the invoker handling /servlet wraps the MultipartWrapper with its own Tomcat-specific wrapper. This allows the parameters to be parsed correctly, but the file access methods won't be directly exposed. In discussing this issue with the Servlet API expert group, we've decided that the servlet container shouldn't do further wrapping beyond the filter's wrapper. The servlet specification will be updated to make that clear. Later betas of Tomcat 4.0 will comply with the clarified rules. A short-term workaround is to use the getRequest() method in the request wrapper to "walk up" the request to find the shadowed multipart wrapper.

Filter power
Servlet filters provide a powerful ability to control request processing and response generation, providing new functionality to servlets without large amounts of servlet coding. I hope these filters have shown you some of what is possible to do with filters, and taught you some tricks about how to make the most effective use of the new filter mechanism.

My thanks to the authors of these filters and to the people who suggested useful filters to write about: Amy Roh, Criag McClanahan, Serge Knystautas, and the folks at OpenSymphony.

About the author
Jason Hunter is a senior technologist with CollabNet, a company that provides tools and services for collaborative software development based on open source concepts. He is author of Java Servlet Programming, 2nd Edition (O'Reilly, April 2001), publisher of Servlets.com, and contributor to Apache Tomcat (starting on the project when it was still Sun internal). He is also a member of the expert groups responsible for Servlet/JSP and JAXP API development, and holds a seat on the JCP Executive Committee overseeing the Java platform, as a representative of the Apache Software Foundation. Most recently he co-created the open source JDOM library to enable optimized Java and XML integration.

To be notified when new articles are added to the site, subscribe here.




 


Home   com.oreilly.servlet   Polls   Lists   
Engines   ISPs   Tools   Docs   Articles   Soapbox   Book

Copyright © 1999-2005 Jason Hunter

webmaster@servlets.com
Last updated: March 1, 2009