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:
import
java.io.*
;
import
javax.servlet.*
;
import
javax.servlet.annotation.*
;
import
javax.servlet.http.*
;
public
class
ConLimitFilter
implements
Filter
{
int
limit
;
volatile
int
count
;
public
void
init
(
FilterConfig
filterConfig
)
throws
ServletException
{
String
s
=
filterConfig
.
getInitParameter
(
"limit"
);
if
(
s
==
null
)
throw
new
ServletException
(
"Missing init parameter: "
+
limit
);
limit
=
Integer
.
parseInt
(
s
);
}
public
void
doFilter
(
ServletRequest
req
,
ServletResponse
res
,
FilterChain
chain
)
throws
IOException
,
ServletException
{
if
(
count
>
limit
)
{
HttpServletResponse
httpRes
=
(
HttpServletResponse
)
res
;
httpRes
.
sendError
(
httpRes
.
SC_SERVICE_UNAVAILABLE
,
"Too Busy."
);
}
else
{
++
count
;
chain
.
doFilter
(
req
,
res
);
--
count
;
}
}
public
void
destroy
()
{
}
}
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.)
import
java.io.*
;
import
javax.servlet.*
;
import
javax.servlet.http.*
;
public
class
WaitServlet
extends
HttpServlet
{
public
void
doGet
(
HttpServletRequest
request
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
String
waitStr
=
request
.
getParameter
(
"time"
);
if
(
waitStr
==
null
)
throw
new
ServletException
(
"Missing parameter: time"
);
int
wait
=
Integer
.
parseInt
(
waitStr
);
try
{
Thread
.
sleep
(
wait
*
1000
);
}
catch
(
InterruptedException
e
)
{
throw
new
ServletException
(
e
);
}
response
.
setContentType
(
"text/html"
);
PrintWriter
out
=
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
:
import
java.io.*
;
import
javax.servlet.*
;
import
javax.servlet.http.*
;
public
class
RequestDefaultsFilter
implements
Filter
{
FilterConfig
filterConfig
;
public
void
init
(
FilterConfig
filterConfig
)
throws
ServletException
{
this
.
filterConfig
=
filterConfig
;
}
public
void
doFilter
(
ServletRequest
req
,
ServletResponse
res
,
FilterChain
chain
)
throws
IOException
,
ServletException
{
WrappedRequest
wrappedRequest
=
new
WrappedRequest
(
(
HttpServletRequest
)
req
);
chain
.
doFilter
(
wrappedRequest
,
res
);
}
public
void
destroy
()
{
}
class
WrappedRequest
extends
HttpServletRequestWrapper
{
WrappedRequest
(
HttpServletRequest
req
)
{
super
(
req
);
}
public
String
getParameter
(
String
name
)
{
String
value
=
super
.
getParameter
(
name
);
if
(
value
==
null
)
value
=
filterConfig
.
getInitParameter
(
name
);
return
value
;
}
}
}
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:
import
java.io.*
;
import
java.util.*
;
import
javax.servlet.*
;
import
javax.servlet.http.*
;
public
class
LinkResponseFilter
implements
Filter
{
FilterConfig
filterConfig
;
public
void
init
(
FilterConfig
filterConfig
)
throws
ServletException
{
this
.
filterConfig
=
filterConfig
;
}
public
void
doFilter
(
ServletRequest
req
,
ServletResponse
res
,
FilterChain
chain
)
throws
IOException
,
ServletException
{
WrappedResponse
wrappedResponse
=
new
WrappedResponse
(
(
HttpServletResponse
)
res
);
chain
.
doFilter
(
req
,
wrappedResponse
);
wrappedResponse
.
close
();
}
public
void
destroy
()
{
}
class
WrappedResponse
extends
HttpServletResponseWrapper
{
boolean
linkText
;
PrintWriter
client
;
WrappedResponse
(
HttpServletResponse
res
)
{
super
(
res
);
}
public
void
setContentType
(
String
mime
)
{
super
.
setContentType
(
mime
);
if
(
mime
.
startsWith
(
"text/html"
)
)
linkText
=
true
;
}
public
PrintWriter
getWriter
()
throws
IOException
{
if
(
client
==
null
)
if
(
linkText
)
client
=
new
LinkWriter
(
super
.
getWriter
(),
new
ByteArrayOutputStream
()
);
else
client
=
super
.
getWriter
();
return
client
;
}
void
close
()
{
if
(
client
!=
null
)
client
.
close
();
}
}
class
LinkWriter
extends
PrintWriter
{
ByteArrayOutputStream
buffer
;
Writer
client
;
LinkWriter
(
Writer
client
,
ByteArrayOutputStream
buffer
)
{
super
(
buffer
);
this
.
buffer
=
buffer
;
this
.
client
=
client
;
}
public
void
close
()
{
try
{
flush
();
client
.
write
(
linkText
(
buffer
.
toString
()
)
);
client
.
close
();
}
catch
(
IOException
e
)
{
setError
();
}
}
String
linkText
(
String
text
)
{
Enumeration
en
=
filterConfig
.
getInitParameterNames
();
while
(
en
.
hasMoreElements
()
)
{
String
pattern
=
(
String
)
en
.
nextElement
();
String
value
=
filterConfig
.
getInitParameter
(
pattern
);
text
=
text
.
replaceAll
(
pattern
,
"<a href="
+
value
+
">$0</a>"
);
}
return
text
;
}
}
}
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 String
replaceAll()
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.
Get Learning Java, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.