Filters in the Servlet 2.3 API

Filters in the Servlet 2.3 API

By Dan Troesser, OCI Senior Software Engineer

May 2001


Java Servlets have been around for several years now and have proven themselves in delivering robust and portable web applications. Since their introduction, the Java web-development front hasn't slowed down.

Along came JSPs, introducing an easier way to generate HTML. XML and XSLT entered the picture making it easier to separate presentation from data, among many other benefits.

The Servlet API hasn't stood still either. The Servlet 2.3 API, now a proposed final draft, will be released soon and will bring with it another powerful feature called filters.

Filters allow declarative pre-processing and post-processing of requests and responses handled by web resources. Web resources in this case are considered servlets, JSPs, static content (HTML files), and even other filters in the web application. Declarative means the Filter can be applied in the deployment descriptor of the web application rather than programmatically in a Servlet or JSP.

Filters are similar to the concept of Servlet chaining introduced and implemented by some Servlet containers years ago. They could also be applied declaratively, usually in a web administration tool.

However, declarative Servlet chaining was not in the specification and thus was not implemented widely or in any standard way by the containers. In the interest of portability, their use somewhat disappeared.

Many Possibilities

The capabilities of filters are many. They are capable of intercepting requests to one or more web resources, performing some action, modifying the request, and passing it on to the next Filter in the chain or to the requested resource. They can also block the request.

Once a request has been served by the requested resource, the same or other filters can intercept the response, perform some action, modify the response, and pass it on. They can also block the response.

Some potential uses of filters include:

Classes Involved

Several classes and interfaces have been added to the Servlet API to help with filtering. They include:

Image Removal Filter Example

This example demonstrates a Filter that removes images from HTML content and replaces them with hyperlinks to the images. This might be useful for users who have low bandwidth (e.g., a 56K modem).

The example demonstrates interception of a response, forwarding to the next resource in the chain, and interception and selective transformation of the response's content.

Let's suppose we have a web application that is used to create and display a photo album. Below is a screenshot of the unfiltered photo album.

screenshot before

We want to filter the photo album for our low-bandwidth users, so the following is produced:

screenshot after

We start by creating a class that implements the Filter interface.

  1. import java.io.*;
  2. import javax.servlet.*;
  3. import javax.servlet.http.*;
  4. import javax.xml.transform.*;
  5. import javax.xml.transform.stream.*;
  6.  
  7. public class ImageRemovalFilter implements Filter {
  8.  
  9. // Could also use an initialization parameter for this.
  10. private static final String STYLESHEET = "removeImages.xslt";
  11. private FilterConfig filterConfig;
  12.  
  13. public void doFilter(ServletRequest req, ServletResponse res,
  14. FilterChain chain) throws IOException, ServletException {
  15. boolean doRemove = req.getParameter("removeImages") != null;
  16. HttpServletResponse httpRes;
  17. if (res instanceof HttpServletResponse) {
  18. httpRes = (HttpServletResponse) res;
  19. } else {
  20. throw new ServletException("ImageRemovalFilter only accepts"
  21. + " HTTP requests");
  22. }
  23. OutputCaptureResponseWrapper resWrapper
  24. = new OutputCaptureResponseWrapper(httpRes);
  25. chain.doFilter(req, resWrapper);
  26. resWrapper.flushBuffer();
  27. String contentType = resWrapper.getContentType();
  28. if (doRemove && contentType != null
  29. && contentType.indexOf("html") != -1) {
  30. String initialXHTML = resWrapper.getOutputAsString();
  31. String finalXHTML = removeImages(initialXHTML);
  32. res.setContentLength(finalXHTML.length());
  33. res.getWriter().print(finalXHTML);
  34. } else {
  35. // Let all other content types pass through.
  36. res.getOutputStream().write(resWrapper.getOutputAsByteArray());
  37. }
  38. }
  39.  
  40. public void init(FilterConfig filterConfig) {
  41. this.filterConfig = filterConfig;
  42. }
  43.  
  44. // Required by Filter interface. Just providing empty implementation.
  45. public void destroy() { }
  46.  
  47. /**
  48.   * Performs an XSLT transformation using JAXP 1.1 to replace img elements
  49.   * in the input XHTML document with links (<a href="...).
  50.   */
  51. private String removeImages(String initialXHTML)
  52. throws ServletException {
  53. StringWriter finalXHTMLWriter = new StringWriter();
  54. TransformerFactory factory = TransformerFactory.newInstance();
  55. ServletContext context = filterConfig.getServletContext();
  56. try {
  57. InputStream xsltStream = context.getResourceAsStream(STYLESHEET);
  58. StreamSource xsltSource = new StreamSource(xsltStream);
  59. StringReader xmlSourceReader
  60. = new StringReader(initialXHTML);
  61. StreamSource xmlSource = new StreamSource(xmlSourceReader);
  62. StreamResult xmlResult = new StreamResult(finalXHTMLWriter);
  63. Transformer transformer = factory.newTransformer(xsltSource);
  64. transformer.transform(xmlSource, xmlResult);
  65. } catch (Exception ex) {
  66. throw new ServletException("Error performing transformation to remove"
  67. + " images", ex);
  68. }
  69. return finalXHTMLWriter.toString();
  70. }
  71. }

When the Filter is loaded, the init() method is called.

We use the opportunity to save a reference to the FilterConfig object. We will need this later to retrieve an instance of the ServletContext object so we can gain access to the XSLT stylesheet, which will perform the content transformation (download the stylesheet here.)

The stylesheet filename is defined as a static final String. It could also have been specified as a Filter initialization parameter in the web application's deployment descriptor (web.xml). If that were the case, the init() method would have been a good place to retrieve the value using getInitParameter() on the FilterConfig object.

Once the Filter is loaded and the init() method is called, the Filter can handle requests. Each request that should be filtered calls the doFilter() method.

The doFilter() method first checks the request object for a removeImages parameter. If it exists, we know we should attempt to remove the images from the response.

Perhaps a better alternative than requiring a request parameter would be to compare the user against a list of users or remote hosts who have low bandwidth. A list of known low-bandwidth remote hosts could be specified as initialization parameters for the Filter.

The getRemoteHost() or getRemoteAddr() method on the request can be used to return the client's hostname or IP address and then compare it against the list.

Rather than maintain a list of remote hosts, one could also develop a user-preferences Servlet or Filter. The user could choose a "remove images" option in their preferences. When the photo album is viewed, the Filter could identify the user, perhaps by calling getRemoteUser() on the request and checking the user's preferences to determine whether to replace the images with links.

These options would provide a more declarative solution. This example uses a request parameter for simplicity.

We only want to handle HTTP requests, so the next step checks whether the request coming in is an HTTP request by checking the type of the response object passed.

The existence of an HttpFilter interface would be nice in this situation. It seems messy to have to check this. Within this check we must also explicitly cast the response to an HttpServletResponse type. This is necessary because our custom-built response wrapper, OutputCaptureResponseWrapper, extends HttpServletResponseWrapper, which wraps an HttpServletResponse.

We then create an instance of OutputCaptureResponseWrapper so that it can be passed along in place of the regular response object.

Calling doFilter() on the FilterChain object passed to us in our doFilter() method continues the chain so that the next Filter or target resource will be called.

The target resource in this case is a Servlet called ViewerServlet that emulates producing dynamic content: Click here to download ViewerServlet.java. // Click here to download the HTML for photos.html.

Note that for the XSL transformation to take place, it must take well-formed XHTML as input. If you look at the HTML file you will notice the elements are properly nested, properly terminated, and the attributes are quoted. This shouldn't be a problem though. We all should be producing well-formed XHTML now, right?

The OutputCaptureResponseWrapper class uses a custom ServletOutputStream called ByteArrayServletOutputStream. These two classes allow the capture and retrieval of the response output.

This is another area where the Servlet API/Specification needs to mature. It seems unnecessary we should have to build our own classes just to capture the output. On the plus side, the classes are not too complex and, once created, can be reused.

Use the links below to view or download the code for these two classes.

Now that we've called the doFilter() method and have captured the content produced by the resources farther down the chain with our custom response object, we can retrieve that content and modify it.

The buffer is flushed to make sure we get all the output when we retrieve it as a String or byte array. We only want to remove images from HTML content, so we make sure the content type is "text/html." Otherwise, we just let the content pass through.

To pass the content along, modified or unmodified, just use the Writer or OutputStream of the original response object. If it is not known if the content is text, make sure to use the OutputStream and pass the data as bytes, not as a String.

It isn't always necessary to use wrapper request or response objects in filters.

The Filter developer may want to simply retrieve request or response values, block further execution, or return completely different content, such as a login or error page. In these cases, using wrapper classes isn't necessary.

Wrapper classes are used when the Filter must modify what the request or response returns. For example, in our OutputCaptureResponseWrapper, we needed to override the methods related to the OutputStream and Writer used for outputting content. Notably, the getOutputStream() and getWriter() methods returned our custom versions (which captured output), rather than the standard. The target resource will then be using our custom OutputSream and Writer.

Deployment

The Filter is useless until we apply it to something. That's where the deployment descriptor comes in.

The deployment descriptor (web.xml) for the image filter example is below.

  1. <?xml version="1.0" encoding="ISO-8859-1"?>
  2. <!DOCTYPE web-app
  3. PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  4. "http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
  5. <web-app>
  6.  
  7. <filter>
  8. <filter-name>Image Removal Filter</filter-name>
  9. <filter-class>ImageRemovalFilter</filter-class>
  10. </filter>
  11.  
  12. <!-- Will cause the Filter to be applied to Viewer Servlet because it
  13. has a matching url-pattern. One could have instead specified the
  14. Servlet directly using a servlet-name element.-->
  15. <filter-mapping>
  16. <filter-name>Image Removal Filter</filter-name>
  17. <url-pattern>/view/*</url-pattern>
  18. </filter-mapping>
  19.  
  20. <!-- Will cause the Filter to be applied to all resources in the web
  21. application, including static content and JSPs. This makes the
  22. previous filter-mapping a bit redundant.-->
  23. <filter-mapping>
  24. <filter-name>Image Removal Filter</filter-name>
  25. <url-pattern>/*</url-pattern>
  26. </filter-mapping>
  27.  
  28. <servlet>
  29. <servlet-name>Viewer Servlet</servlet-name>
  30. <servlet-class>ViewerServlet</servlet-class>
  31. </servlet>
  32.  
  33. <servlet-mapping>
  34. <servlet-name>Viewer Servlet</servlet-name>
  35. <url-pattern>/view/*</url-pattern>
  36. </servlet-mapping>
  37.  
  38. </web-app>

The <filter> element declares a Filter in the web application.

It expects <filter-name> and <filter-class> child elements that define the logical name and class of the Filter, respectively, similar to the definitions for servlets shown farther down in the file.

Initialization parameters can be defined in the same way for filters as for servlets using <init-params> elements. We also have <filter-mapping> elements that look similar to <servlet-mapping> elements. They can contain <url-pattern> elements that define when a Filter should be called.

Filters have critical differences, however, when it comes to mapping.

For one, the <filter-mapping> element can take a <servlet-name> element instead of a <url-pattern>element. This allows the Filter to be mapped to an individual Servlet using its logical name defined elsewhere in the deployment descriptor.

Another critical difference in mapping is realized when filters are chained together. When chaining multiple filters, the order the mappings appear in the deployment descriptor is significant.

First, the filters will be called in the order the <filter-mapping> elements appear for <url-pattern> elements that match. Then filters will be called in the order the <filter-mapping> elements appear for matching <servlet-name> elements.

Great Potential

Improvements could be made to the API to make it more Filter developer friendly. The addition of an HttpFilter would be nice. It would also be nice to see more wrapper classes or a modification of the existing to make it easier to capture the output of the response for possible modification. Until then, developers will have to develop their own classes similar to those in the example.

Despite some possible tweaking in the specification, filters have the potential to change the way web applications are designed and implemented. Developers now have an alternative to programmatically forcing some or all requests through a single Servlet or custom JSP tag.

Once filters are created, they can be applied in the deployment descriptor to any single resource or group of resources in a web application. Filters can also be chained together. In this way, filters and their accompanying pre- and post-processing can be easily applied and reused among resources in a web application. Things like logging, modification of headers, transforming of content, and validation no longer need to be coded into servlets or JSPs. This will allow web applications to become even more modular and easier to develop.

References

Note: The example used Jakarta Tomcat 4.0-b4-dev, JAXP 1.1, Xerces 1.3.1, and Xalan 2.0.1.



Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.


secret