|
Related Articles: |
This article is the third in a series dedicated to examining the new features of the Servlet 2.3 API specification. The first article was a high level introduction to Servlet concepts and the new features of the Servlet 2.3 specification: application lifecycle events and filters. The second article took a more in-depth look at writing and using application lifecycle events. Please read these articles for more information on those topics. This article covers how to write and use filters within your web applications, including examples of using a simple filter, configuring a filter chain, using initial parameters in a filter, parsing the user's request, and logging statistics.
Servlet filters are a new addition to the Servlet 2.3 specification that's in its public final draft stage, released in October, 2000. Filters are Java classes that can intercept requests from a client before they access a resource; manipulate requests from clients; intercept responses from resources before they are sent back to the client; and manipulate responses before they are sent to the client.
Filters have a wide array of uses; the Servlet 2.3 specification suggests the following uses:
The first step in learning how to write a filter is to look at a
very simple example. A filter is simply a Java class that implements
the javax.servlet.Filter interface. The
javax.servlet.Filter interface defines three methods:
public void doFilter (ServletRequest request,
ServletResponse response, FilterChain chain)public FilterConfig getFilterConfig()public void setFilterConfig (FilterConfig
filterConfig)It is the containers responsibility to create a
javax.servlet.FilterConfig object and pass it to the
filter during initialization. The
javax.servlet.FilterConfig object can be used to
ServletContext object the user
is calling from.The setFilterConfig() method can be used to capture
the object into an attribute of the filter. The
doFilter() method is where the filter's work is done, in
which you can parse the user's request; log statistics to a file;
manipulate the response back to the client; and so on. Listing 1 is an
example of a very simple filter which prints a message to the
console of the web server while it is filtering the request, calls the
Servlet, and then prints another message to the console while it is
filtering the response. Figure 1 is a diagram of how the simple
filter fits into the servlet's request-response model.
|
Listing 1: An example of a very simple filter (SimpleFilter.java)
package com.filters;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import javax.servlet.ServletException;
public class SimpleFilter implements Filter
{
private FilterConfig filterConfig;
public void doFilter (ServletRequest request,
ServletResponse response,
FilterChain chain)
{
try
{
System.out.print ("Within Simple Filter ... ");
System.out.println ("Filtering the Request ...");
chain.doFilter (request, response);
System.out.print ("Within Simple Filter ... ");
System.out.println ("Filtering the Response ...");
} catch (IOException io) {
System.out.println ("IOException raised in SimpleFilter");
} catch (ServletException se) {
System.out.println ("ServletException raised in SimpleFilter");
}
}
public FilterConfig getFilterConfig()
{
return this.filterConfig;
}
public void setFilterConfig (FilterConfig filterConfig)
{
this.filterConfig = filterConfig;
}
}
As you can see the simple filter example is a Java class named
SimpleFilter.java which implements the
javax.servlet.Filter interface. It provides
implementation for the three methods defined in the
javax.servlet.Filter interface.
Notice that doFilter() can be separated into two
sections: filtering the request and filtering the response. The two
sections are separated by a call from the
javax.servlet.FilterChain object to the next object in
the chain, which could be the Servlet or another filter.
Now that we have written the filter, it might be nice to deploy it in a web server and see it in action.
Apache's Tomcat, version 4.0 beta, supports the Servlet 2.3 specification and all the examples in this article have been tested on that server.
|
The filters in a web application are defined in the deployment
descriptor of the web application, the web.xml
file. Filters are defined and then mapped to a URL or Servlet, in much
the same was as Servlet is defined and then mapped to a URL
pattern. Listing 2 shows the web.xml file that deployed
the SimpleFilter.java from Listing 1, mapping it to a
Servlet.
Listing 2: The deployment descriptor for the simple filter. (web.xml)
<?xml version = "1.0" encoding = "ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
<web-app>
<!-- Define the filters within the Web Application -->
<filter>
<filter-name>
Simple Filter Example
</filter-name>
<filter-class>
com.filters.SimpleFilter
</filter-class>
</filter>
<!-- Map the filter to a Servlet or URL -->
<filter-mapping>
<filter-name>
Simple Filter Example
</filter-name>
<url-pattern>
/simple
</url-pattern>
</filter-mapping>
<!-- Define the Servlets within the Web Application -->
<servlet>
<servlet-name>
Simple Servlet
</servlet-name>
<servlet-class>
com.servlets.SimpleServlet
</servlet-class>
</servlet>
<!-- Define Servlet mappings to urls -->
<servlet-mapping>
<servlet-name>
Simple Servlet
</servlet-name>
<url-pattern>
/simple
</url-pattern>
</servlet-mapping>
</web-app>
Figure 2 shows the console of the Tomcat server where this web application was running after the servlet had handled a request.
|
In the first example only one filter that handled the request and response for the simple servlet. What if you want multiple filters to handle the request and response? Figure 3 shows what a filter chain looks like.
|
Multiple filters can be written and applied to the same URL
pattern. The order of execution is determined by the ordering in the
deployment descriptor. Remember that in the doFilter()
method you call the next element in the chain with
chain.doFilter(request, response)
|
Listing 3 shows the code for Filter1.java, which is a
simple filter that outputs to the console of the web server a
message. The message allows you to track the request through the
filters.
Listing 3: A simple filter that outputs a message to the console. (Filter1.java)
package com.filters;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import javax.servlet.ServletException;
public class Filter1 implements Filter
{
private FilterConfig filterConfig;
public void doFilter (ServletRequest request,
ServletResponse response,
FilterChain chain)
{
try
{
System.out.print ("Within First Filter ... ");
System.out.println ("Filtering the Request ...");
chain.doFilter (request, response);
System.out.print ("Within First Filter ... ");
System.out.println ("Filtering the Response ...");
} catch (IOException io) {
System.out.println ("IOException raised in Filter1 Filter");
} catch (ServletException se) {
System.out.println ("ServletException raised in Filter1 Filter");
}
}
public FilterConfig getFilterConfig()
{
return this.filterConfig;
}
public void setFilterConfig (FilterConfig filterConfig)
{
this.filterConfig = filterConfig;
}
}
The second filter prints a message to the console of the web server to help us track the request and response. Listing 4 shows the code for the second filter in the chain.
Listing 4. A simple filter that outputs a message to the console. (Filter2.java)
package com.filters;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import javax.servlet.ServletException;
public class Filter2 implements Filter
{
private FilterConfig filterConfig;
public void doFilter (ServletRequest request,
ServletResponse response,
FilterChain chain)
{
try
{
System.out.print ("Within Second Filter ... ");
System.out.println ("Filtering the Request ...");
chain.doFilter (request, response);
System.out.print ("Within Second Filter ... ");
System.out.println ("Filtering the Response ...");
} catch (IOException io) {
System.out.println ("IOException raised in Filter2 Filter");
} catch (ServletException se) {
System.out.println ("ServletException raised in Filter2 Filter");
}
}
public FilterConfig getFilterConfig()
{
return this.filterConfig;
}
public void setFilterConfig (FilterConfig filterConfig)
{
this.filterConfig = filterConfig;
}
}
|
After writing the filters, they need to be defined and mapped to a
URL pattern in the deployment descriptor. The order in which they are
defined matters. The container will execute the filters in the order
in which they are defined. Listing 5 shows the web.xml file for
deploying Filter1.java and Filter2.java.
Listing 5: The deployment descriptor for the filter chaining. (web.xml)
<?xml version = "1.0" encoding = "ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
<web-app>
<!-- Define the filters within the Web Application -->
<filter>
<filter-name>
First Filter in Chain
</filter-name>
<filter-class>
com.filters.Filter1
</filter-class>
</filter>
<filter>
<filter-name>
Second Filter in Chain
</filter-name>
<filter-class>
com.filters.Filter2
</filter-class>
</filter>
<!-- Map the filter to a Servlet or URL -->
<filter-mapping>
<filter-name>
First Filter in Chain
</filter-name>
<url-pattern>
/simple
</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>
Second Filter in Chain
</filter-name>
<url-pattern>
/simple
</url-pattern>
</filter-mapping>
<!-- Define the Servlets within the Web Application -->
<servlet>
<servlet-name>
Simple Servlet
</servlet-name>
<servlet-class>
com.servlets.SimpleServlet
</servlet-class>
</servlet>
<!-- Define Servlet mappings to urls -->
<servlet-mapping>
<servlet-name>
Simple Servlet
</servlet-name>
<url-pattern>
/simple
</url-pattern>
</servlet-mapping>
</web-app>
The output of the messages in the console is shown in Figure
5. Notice the request went through Filter1 and then
Filter2, but the response went through
Filter2 and then Filter1.
|
So far the examples of the filters have been trivial. Now it's time to do something more realistic. Sometimes you may want to use a filter to verify certain objects pass with the requests, such as elements from an HTML form. You may want to verify the existence and validity of the values. If they don't exist, you send the client's request to one servlet. If they do exist but the values are not valid, then they are sent to a different servlet. Finally, if the elements check out, they are passed to the requested servlet. Figure 6 shows a diagram of this scenario.
|
Let's take this example one step further. Not only do we want to verify HTML form data, we want to retrieve the names of the HTML form elements from initialization parameters. This means that if the HTML form changes, we do not need to change any code in the filter; we simply change the initialization parameters for the filter.
Initialization parameters can be specified in the deployment descriptor. They are specified when the filter is defined. Listing 6 is the code for the filter that retrieves HTML form element names from initialization parameters, verifies the form data, and sends the request to the appropriate servlet.
Listing 6: A filter that verifies HTML form data. (RequestFilter.java)
package com.filters;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.RequestDispatcher;
import java.util.Enumeration;
import java.io.IOException;
import javax.servlet.ServletException;
public class RequestFilter implements Filter
{
private FilterConfig filterConfig;
public void doFilter (ServletRequest request,
ServletResponse response,
FilterChain chain)
{
RequestDispatcher rd = null;
boolean emptyform = false;
try
{
Enumeration initParams = filterConfig.getInitParameterNames();
// no initial parameters so invoke next element in chain
if (initParams == null)
{
System.out.println("No elements to verify");
chain.doFilter(request, response);
}
// grab init param values and get the form elements
else
{
while (initParams.hasMoreElements())
{
String name = (String) initParams.nextElement();
String value = filterConfig.getInitParameter(name);
String formElement = request.getParameter(value);
// check to see if element exists (i.e. form was sent)
if (formElement == null)
{
rd = request.getRequestDispatcher("/noform");
rd.forward(request, response);
}
// check to see if elements are empty
else if (formElement.equals(""))
{
emptyform = true;
}
}
// a form element was empty
if (emptyform)
{
rd = request.getRequestDispatcher("/emptyform");
rd.forward(request, response);
}
// form was filled out properly
else
{
chain.doFilter(request, response);
}
}
}
catch (IOException io)
{
System.out.println("IOException raised");
}
catch (ServletException se)
{
System.out.println("ServletException raised");
}
}
public FilterConfig getFilterConfig()
{
return this.filterConfig;
}
public void setFilterConfig (FilterConfig filterConfig)
{
this.filterConfig = filterConfig;
}
}
|
The web.xml to deploy this filter and define
initial parameters is shown in Listing 7.
Listing 7: The deployment descriptor that defines initial parameters for a filter. (web.xml)
<?xml version = "1.0" encoding = "ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
<web-app>
<!-- Define the filters within the Web Application -->
<filter>
<filter-name>
Parsing Request Data
</filter-name>
<filter-class>
com.filters.RequestFilter
</filter-class>
<init-param>
<param-name>
User's Name
</param-name>
<param-value>
myname
</param-value>
</init-param>
<init-param>
<param-name>
User's Email
</param-name>
<param-value>
txtemail
</param-value>
</init-param>
</filter>
<!-- Map the filter to a Servlet or URL -->
<filter-mapping>
<filter-name>
Parsing Request Data
</filter-name>
<url-pattern>
/formprocessor
</url-pattern>
</filter-mapping>
<!-- Define the Servlets within the Web Application -->
<servlet>
<servlet-name>
No Form Submitted
</servlet-name>
<servlet-class>
com.servlets.NoElements
</servlet-class>
</servlet>
<servlet>
<servlet-name>
Empty Form Elements
</servlet-name>
<servlet-class>
com.servlets.InvalidEntries
</servlet-class>
</servlet>
<servlet>
<servlet-name>
Good Request
</servlet-name>
<servlet-class>
com.servlets.MyServlet
</servlet-class>
</servlet>
<!-- Define Servlet mappings to urls -->
<servlet-mapping>
<servlet-name>
No Form Submitted
</servlet-name>
<url-pattern>
/noform
</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>
Empty Form Elements
</servlet-name>
<url-pattern>
/emptyform
</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>
Good Request
</servlet-name>
<url-pattern>
/formprocessor
</url-pattern>
</servlet-mapping>
</web-app>
The Servlet 2.3 API specification provided important new
features. Application lifecycle events provide the developer
better control over the ServletContext object and
HttpSession objects. Filters provide a way to take
control logic out of servlets, placing it in more reusable pieces
of code.
Stephanie Fesler is a BEA Systems expert on implementing various Java 2EE API.
Return to ONJava.com.
Copyright © 2009 O'Reilly Media, Inc.