Filter code with Servlet 2.3 model

Discover freely available servlet filters you can use today

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 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 nondescriptively 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.xmldeployment 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 timerFilteris implemented in the TimerFilterclass. 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 server (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 beta 5, you'll find the log file under server_root/logs/.

Download the WAR file for this filter at http://www.javaworld.com/jw-06-2001/Filters/timer.war.

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 ClickstreamFilterclass 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 wonder how the filter knows that the clickstreamattribute will be present in the session. That's because the ClickstreamLogger places it there when the session is created. Here is the ClickstreamLoggercode:

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.jsppage 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 Clickstreamto 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.jspfile 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 at http://www.javaworld.com/jw-06-2001/Filters/clickstream.war.

For the filter to work on Tomcat 4.0 beta 5, 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:

1 2 3 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more