The servlet Filter API generalizes the Java Servlet API to allow modular component “filters” to operate on the servlet request and responses in a sort of pipeline. Filters are chained, meaning that when more than one filter is applied, the servlet request is passed through each filter in succession, with each having an opportunity to act upon or modify the request before passing it to the next filter. Similarly, upon completion, the servlet result is effectively passed back through the chain on its return trip to the browser. Servlet filters may operate on any requests to a web application, not just those handled by the servlets; they may filter static content, as well. You can also control whether filters are applied to error and welcome pages as well as pages forwarded or included using the request dispatcher (from servlet to servlet).
Filters can be declared and mapped to servlets in the web.xml file or using annotations. There are two ways to map a filter: using a URL pattern like those used for servlets or by specifying a servlet by its servlet name as defined in its servlet config. Filters obey the same basic rules as servlets when it comes to URL matching, but when multiple filters match a path, they are each invoked.
When using web.xml, the order of the chain is
determined by the order in which matching filter mappings appear in the
web.xml file, with <url-pattern>
matches taking precedence over <servlet-name>
matches. This is contrary to the way in which servlet URL matching is
done, with specific matches taking the highest priority. Filter chains are
constructed as follows. First, each filter with a matching URL pattern is
called in the order in which it appears in the
web.xml file; next, each filter with a matching
servlet name is called, also in order of appearance. URL patterns take a
higher priority than filters specifically associated with a servlet, so in
this case, patterns such as /* have
first crack at an incoming request.
Servlet filters may be declared and mapped using the WebFilter annotation.
There is no corresponding way to control filter ordering using
annotations; however, as always you can mix annotations and
web.xml to minimize the XML configuration by only
declaring the filter mappings in the XML. (We’ll discuss configuration
more later in this chapter.)
The Filter API is very simple and mimics the Servlet API. A servlet
filter implements the javax.servlet.Filter
interface and implements three methods: init(), doFilter(), and destroy(). The doFilter() method is where the work is
performed. For each incoming request, the ServletRequest and ServletResponse objects are passed to doFilter(). Here, we have a chance to examine
and modify these objects—or even substitute our own objects for
them—before passing them to the next filter and, ultimately, the servlet
(or user) on the other side. Our link to the rest of the filter chain is
another parameter of doFilter(), the
FilterChain object. With FilterChain, we can invoke the next element in
the pipeline. The following section presents an example.
For our first filter, we’ll do something easy but practical: create a filter that limits the number of concurrent connections to its URLs. We’ll simply have our filter keep a counter of the active connections passing through it and turn away new requests when they exceed a specified limit:
importjava.io.*;importjavax.servlet.*;importjavax.servlet.annotation.*;importjavax.servlet.http.*;publicclassConLimitFilterimplementsFilter{intlimit;volatileintcount;publicvoidinit(FilterConfigfilterConfig)throwsServletException{Strings=filterConfig.getInitParameter("limit");if(s==null)thrownewServletException("Missing init parameter: "+limit);limit=Integer.parseInt(s);}publicvoiddoFilter(ServletRequestreq,ServletResponseres,FilterChainchain)throwsIOException,ServletException{if(count>limit){HttpServletResponsehttpRes=(HttpServletResponse)res;httpRes.sendError(httpRes.SC_SERVICE_UNAVAILABLE,"Too Busy.");}else{++count;chain.doFilter(req,res);--count;}}publicvoiddestroy(){}}
ConLimitFilter implements the
three lifecycle methods of the Filter
interface: init(), doFilter(), and destroy(). In our init() method, we use the FilterConfig object to look for an
initialization parameter named “limit” and turn it into an integer.
Users can set this value in the section of the
web.xml file where the instance of our filter is
declared or in the annotation as shown. The doFilter() method implements all our logic.
First, it receives ServletRequest and
ServletResponse object pairs for
incoming requests. Depending on the counter, it then either passes them
down the chain by invoking the next doFilter() method on the FilterChain object, or rejects them by
generating its own response. We use the standard HTTP message “504
Service Unavailable” when we deny new connections.
Calling doFilter() on the
FilterChain object continues
processing by invoking the next filter in the chain or by invoking the
servlet if ours is the last filter. Alternatively, when we choose to
reject the call, we use the ServletResponse to generate our own response
and then simply allow doFilter() to
exit. This stops the processing chain at our filter, although any
filters called before us still have an opportunity to intervene as the
request effectively traverses back to the client.
Notice that ConLimitFilter
increments the count before calling doFilter() and decrements it after. Prior to
calling doFilter(), we can work on
the request before it reaches the rest of the chain and the servlet.
After the call to doFilter(), the
chain to the servlet has completed, and the request is sent back to the
client. This is our opportunity to do any post-processing of the
response.
Finally, we should mention that although we’ve been talking about
the servlet request and response as if they were HttpServletRequest and HttpServletResponse, the doFilter() method actually takes the more
generic ServletRequest and
ServletResponse objects
as parameters. As filter implementers, we are expected to determine when
it is safe to treat them as HTTP traffic and perform the cast as
necessary (which we do here in order to use the sendError() HTTP response method).
Before we go on, here is a simple test servlet you can use
to try out this filter and the other filters we’ll develop in this
section. It’s called WaitServlet and,
as its name implies, it simply waits. You can specify how long it waits
as a number of seconds with the servlet parameter time. (This is the “dumb” version of the
BackgroundWaitServlet that we created
earlier in this chapter when discussing asynchronous servlets.)
importjava.io.*;importjavax.servlet.*;importjavax.servlet.http.*;publicclassWaitServletextendsHttpServlet{publicvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{StringwaitStr=request.getParameter("time");if(waitStr==null)thrownewServletException("Missing parameter: time");intwait=Integer.parseInt(waitStr);try{Thread.sleep(wait*1000);}catch(InterruptedExceptione){thrownewServletException(e);}response.setContentType("text/html");PrintWriterout=response.getWriter();out.println("<html><body><h1>WaitServlet Response</h1></body></html>");out.close();}}
By making multiple simultaneous requests to the WaitServlet, you can try out the ConLimitFilter. Note that some web browsers
won’t open multiple requests to the same URL or may delay opening
multiple tabs. You may have to add extraneous parameters to trick the
web browser. Alternately, you may wish to use the curl command-line utility to make the
requests if you have it.
In the web.xml file filters are declared and mapped much as servlets are. Like servlets, one instance of a filter class is created for each filter declaration in the web.xml file. A filter declaration looks like this:
<filter><filter-name>defaultsfilter1</filter-name><filter-class>RequestDefaultsFilter</filter-class></filter>
It specifies a filter handle name to be used for reference within
the web.xml file and the filter’s Java class name.
Filter declarations may also contain <init-param>
parameter sections, just like servlet declarations.
Filters are mapped to resources with <filter-mapping>
declarations that specify the filter handle name and either the specific
servlet handle name or a URL pattern, as we discussed earlier:
<filter-mapping><filter-name>conlimitfilter1</filter-name><servlet-name>waitservlet1</servlet-name></filter-mapping><filter-mapping><filter-name>conlimitfilter1</filter-name><url-pattern>/*</url-pattern></filter-mapping>
The corresponding WebFilter
annotation can declare and map filters as well as supply filter
parameters. The annotation will accept either a urlPatterns or a
servletNames attribute
for the mapping.
@WebFilter(urlPatterns="/*",initParams={@WebInitParam(name="limit",value="3")})
Our first filter example was not very exciting because it
did not actually modify any information going to or coming from the
servlet. Next, let’s do some actual “filtering” by modifying the
incoming request before it reaches a servlet. In this example, we’ll
create a request “defaulting” filter that automatically supplies default
values for specified servlet parameters when they are not provided in
the incoming request. Here is the RequestDefaultsFilter:
importjava.io.*;importjavax.servlet.*;importjavax.servlet.http.*;publicclassRequestDefaultsFilterimplementsFilter{FilterConfigfilterConfig;publicvoidinit(FilterConfigfilterConfig)throwsServletException{this.filterConfig=filterConfig;}publicvoiddoFilter(ServletRequestreq,ServletResponseres,FilterChainchain)throwsIOException,ServletException{WrappedRequestwrappedRequest=newWrappedRequest((HttpServletRequest)req);chain.doFilter(wrappedRequest,res);}publicvoiddestroy(){}classWrappedRequestextendsHttpServletRequestWrapper{WrappedRequest(HttpServletRequestreq){super(req);}publicStringgetParameter(Stringname){Stringvalue=super.getParameter(name);if(value==null)value=filterConfig.getInitParameter(name);returnvalue;}}}
To interpose ourselves in the data flow, we must do something
drastic. We kidnap the incoming HttpServletRequest object and replace it with
an imposter that does our bidding. The technique, which we’ll use here
for modifying the request object and later for modifying the response,
is to wrap the real request with an adapter, allowing us to override
some of its methods. Here, we will take control of the HttpServletRequest’s getParameter() method, modifying it to look
for default values where it would otherwise return null.
Again, we implement the three lifecycle methods of Filter, but this time, before invoking
doFilter() on the filter chain to
continue processing, we wrap the incoming HttpServletRequest in
our own class, WrappedRequest.
WrappedRequest extends a special
adapter called HttpServletRequestWrapper. This wrapper class
is a convenience utility that extends HttpServletRequest. It accepts a reference to
a target HttpServletRequest object
and, by default, delegates all of its methods to that target. This makes
it very convenient for us to simply override one or more methods of
interest to us. All we have to do is override getParameter() in our WrappedRequest class and add our
functionality. Here, we simply call our parent’s getParameter(), and in the case where the
value is null, we try to substitute a
filter initialization parameter of the same name.
Try this example using the WaitServlet with a filter declaration and
mapping or annotation as follows:
<filter><filter-name>defaultsfilter1</filter-name><filter-class>RequestDefaultsFilter</filter-class><init-param><param-name>time</param-name><param-value>3</param-value></init-param></filter><filter-mapping><filter-name>defaultsfilter1</filter-name><servlet-name>waitservlet1</servlet-name></filter-mapping>@WebFilter(servletNames="waitservlet1",initParams={@WebInitParam(name="time",value="3")})
Now the WaitServlet receives a
default time value of three seconds even when you don’t specify
one.
Filtering the request was fairly easy, and we can do
something similar with the response object using exactly the same
technique. There is a corresponding HttpServletResponseWrapper that we can use to
wrap the response before the servlet uses it to communicate back to the
client. By wrapping the response, we can intercept methods that the
servlet uses to write the response, just as we intercepted the getParameter() method that the servlet used in
reading the incoming data. For example, we could override the sendError() method of the HttpServletResponse object and modify it to
redirect to a specified page. In this way, we could create a servlet
filter that emulates the programmable error page control offered in the
web.xml file. But the most interesting technique
available to us, and the one we’ll show here, involves actually
modifying the data written by the servlet before it reaches the client.
In order to do this, we have to pull a double “switcheroo.” We wrap the
servlet response to override the getWriter() method and then create our own
wrapper for the client’s PrintWriter
object supplied by this method, one that buffers the data written and
allows us to modify it. This is a useful and powerful technique, but it
can be tricky.
Our example, LinkResponseFilter, is an automatic
hyperlink-generating filter that reads HTML responses and searches them
for patterns supplied as regular expressions. When it matches a pattern,
it turns it into an HTML link. The pattern and links are specified in
the filter initialization parameters. You could extend this example with
access to a database or XML file and add more rules to make it into a
useful site-management helper. Here it is:
importjava.io.*;importjava.util.*;importjavax.servlet.*;importjavax.servlet.http.*;publicclassLinkResponseFilterimplementsFilter{FilterConfigfilterConfig;publicvoidinit(FilterConfigfilterConfig)throwsServletException{this.filterConfig=filterConfig;}publicvoiddoFilter(ServletRequestreq,ServletResponseres,FilterChainchain)throwsIOException,ServletException{WrappedResponsewrappedResponse=newWrappedResponse((HttpServletResponse)res);chain.doFilter(req,wrappedResponse);wrappedResponse.close();}publicvoiddestroy(){}classWrappedResponseextendsHttpServletResponseWrapper{booleanlinkText;PrintWriterclient;WrappedResponse(HttpServletResponseres){super(res);}publicvoidsetContentType(Stringmime){super.setContentType(mime);if(mime.startsWith("text/html"))linkText=true;}publicPrintWritergetWriter()throwsIOException{if(client==null)if(linkText)client=newLinkWriter(super.getWriter(),newByteArrayOutputStream());elseclient=super.getWriter();returnclient;}voidclose(){if(client!=null)client.close();}}classLinkWriterextendsPrintWriter{ByteArrayOutputStreambuffer;Writerclient;LinkWriter(Writerclient,ByteArrayOutputStreambuffer){super(buffer);this.buffer=buffer;this.client=client;}publicvoidclose(){try{flush();client.write(linkText(buffer.toString()));client.close();}catch(IOExceptione){setError();}}StringlinkText(Stringtext){Enumerationen=filterConfig.getInitParameterNames();while(en.hasMoreElements()){Stringpattern=(String)en.nextElement();Stringvalue=filterConfig.getInitParameter(pattern);text=text.replaceAll(pattern,"<a href="+value+">$0</a>");}returntext;}}}
That was a bit longer than our previous examples, but the basics
are the same. We wrapped the HttpServletResponse object with our own
WrappedResponse class using the
HttpServletResponseWrapper helper
class. Our WrappedResponse overrides
two methods: getWriter() and setContentType(). We override setContentType() in order to set a flag that
indicates whether the output is of type “text/html” (an HTML document).
We don’t want to be performing regular-expression replacements on binary
data such as images, for example, should they happen to match our
filter. We also override getWriter()
to provide our substitute writer stream, LinkWriter. Our LinkWriter class is a PrintStream that takes as arguments the client
PrintWriter and a ByteArrayOutputStream that serves as a buffer
for storing output data before it is written. We are careful to
substitute our LinkWriter only if the
linkText Boolean set by setContent() is true. When we do use our LinkWriter, we cache the stream so that any
subsequent calls to getWriter()
return the same object. Finally, we have added one method to the
response object: close(). A normal
HttpServletResponse
does not have a close() method. We
use ours on the return trip to the client to indicate that the LinkWriter should complete its processing and
write the actual data to the client. We do this in case the client does
not explicitly close the output stream before exiting the servlet
service methods.
This explains the important parts of our filter-writing example.
Let’s wrap up by looking at the LinkWriter, which does the magic in this
example. LinkWriter is a PrintStream that holds references to two other
Writers: the true client PrintWriter and a ByteArrayOutputStream.
The LinkWriter calls its superclass
constructor, passing the ByteArrayOutputStream as the target stream, so
all of its default functionality (its print() methods) writes to the byte array. Our
only real job is to intercept the close() method of the PrintStream and add our text linking before
sending the data. When LinkWriter is
closed, it flushes itself to force any data buffered in its superclass
out to the ByteArrayOutputStream. It
then retrieves the buffered data (with the ByteArrayOutputStream toString() method) and
invokes its linkText() method to
create the hyperlinks before writing the linked data to the client. The
linkText() method simply loops over
all the filter initialization parameters, treating them as patterns, and
uses the StringreplaceAll() method to turn them into
hyperlinks. (See Chapter 1 for more about
replaceAll().)
This example works, but it has limitations. First, we cannot
buffer an infinite amount of data. A better implementation would make a
decision about when to start writing data to the client, potentially
based on the client-specified buffer size of the HttpServletResponse API. Next, our
implementation of linkText() could
probably be speeded up by constructing one large regular expression
using alternation. You will undoubtedly find other ways in which it can
be improved.