In our last post, we went through the creation of a very basic Java Servlet, and bundled it up into an equally simple Java web application. We’ll go over some more complicated Servlet examples soon, but for now I want to take a look at one of the other components of the Servlet specification - Filters.
The purpose of a Filter is to intercept requests and responses in order to do something with them. This probably sounds redundant - after all, we already do something with these objects within the Servlet itself. The existence of filters, however, allows us to enforce good, modular design, as well as separation of concerns.
Let me give you an example. Two of the most common uses of Filters is to aid in logging and authentication. Now imagine that you come up with a common logging scheme for all of your Servlets and web apps. If you wrote this code directly into each Servlet, you would be repeating yourself again and again. If instead you place it into into a Filter, it can be reused throughout your projects. By keeping our general purpose code separate from our situation-specific code, we’ll spend less time refactoring and rewriting in the future.
Let’s work through some code. I created a new branch in my Servlet Tutorial Github repository named servlet_filters
, which builds upon our previous example by adding some filters. If you’re not using Git, you can download the branch as a ZIP file here
When we created a Servlet, we wrote a class which extended HttpServlet
. The process for writing a filter is much the same; we simply implement the javax.servlet.Filter
interface. This interface has only three methods, so it is fairly easy to work with. Here is the code for our first, simple filter, which simply logs incoming requests. Pay close attention to the comments above each method, as they describe the method’s purpose:
package net.cmw;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import java.io.IOException;
/**
* Basic Filter example which logs incoming requests.
*/
public class LoggingFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class);
private String testValue;
/**
* This method is called when the filter is created. The FilterConfig object passed in contains
* key/value pairs which can be configured in the web.xml file.
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
testValue = filterConfig.getInitParameter("testValue");
}
/**
* Web apps can have multiple filters, which the server chains together; calling the doFilter() method
* will pass the request and response objects to the next filter in the chain. When it arrives at the last filter,
* the doFilter() method will pass the request and response to the Servlet itself.
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
logger.info("Request incoming from " + request.getRemoteHost());
logger.info("Our test value is " + testValue);
chain.doFilter(request, response);
}
/**
* Called when the filter is being destroyed at web app shutdown. You generally don't have to write anything
* concrete in this method.
*/
@Override
public void destroy() {
}
}
As mentioned in the file’s comments, we don’t write anything into the destroy()
method. In the init()
method, we grab a value from the FilterConfig
object passed in. We’ll see how this object is created when we look at the web.xml
file.
The most important method is doFilter()
, which is where all the real work is done. We log some information, and at the end of the method, we call chain.doFilter()
to inform the filter chain to move on to the next filter. If we fail to call this method, our Servlet will never execute, so don’t forget about it.
Moving onto the web.xml
, we will see that configuring a Filter is not that different than a Servlet:
<?xml version="1.0" encoding="UTF-8"?>
<web-app
version="3.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/
web-app_3_0.xsd" >
<display-name>
Basic Servlet Example
</display-name>
<filter>
<filter-name>LoggingFilter</filter-name>
<filter-class>net.cmw.LoggingFilter</filter-class>
<init-param>
<param-name>testValue</param-name>
<param-value>Test Value!</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>LoggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>Example Servlet</servlet-name>
<servlet-class>
net.cmw.BasicServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Example Servlet</servlet-name>
<url-pattern>/basic</url-pattern>
</servlet-mapping>
</web-app>
We first define the Filter, then declare what URL pattern it maps to. In this case, we’ll map it to /*
, which means that the filter will run for every Servlet in our web app (we only have one now, but we could add more if we wanted to). The other section of note is the <init-param>
block. This is where we can configure the key/value pairs that get added to the FilterConfig
object passed to our filter. If we look back at our code, we see that we retrieve a property named “testValue” from the FilterConfig
. Sure enough, we configured exactly here in the XML.
The configuration above contains only one Filter definition, but naturally we could have as many as we want. It is important to understand that the web server will create the Filter Chain based on the order in which the Filters are defined in web.xml
. Keep this in mind if you want (or need) certain Filters to execute before others.
If we run the web application, we’ll see no change in what the Servlet returns. However, take a look at the log output from your server, and you should see the log messages we created in our Filter. Here’s the example output from my terminal:
Looks like it is working. We’ve now written a Filter which will run before any Servlet we create. This wouldn’t have been as easy if we wrote this same behavior directly into our BasicServlet
.
So far so good, but we have one more example I’d like to go over. I mentioned that Filters can be used not only to intercept incoming requests, but also to manipulate outgoing responses. We need to examine how to perform the latter of the two, as it is a bit different (and a bit more complicated) than what we just did.
I told you earlier that you have to make a call to chain.doFilter()
in order for your Filter to work properly. However, I didn’t say that it has to be the last call in your Filter, because the truth is that it doesn’t. You can still do stuff after chain.doFilter()
finishes, including accessing the response object.
Knowing this, you may be tempted to modify our doFilter()
method to look like this:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
logger.info("Request incoming from " + request.getRemoteHost());
logger.info("Our test value is " + testValue);
chain.doFilter(request, response);
PrintWriter writer = response.getWriter();
writer.write("Our test value is " + testValue + "\n");
writer.write(writer.toString());
writer.write("\nIsn't that great?");
}
If you were to compile this and re-run the webapp, the response might look correct, but you’d most definitely see the following stack trace in the logs:
java.lang.IllegalStateException: STREAM
at org.mortbay.jetty.Response.getWriter( Response.java:616)
at net.cmw.LoggingFilter.doFilter( LoggingFilter.java:40)
...
This leads us to the next important point about the interaction between Servlets and Filters. When a Servlet finishes its work, it will close the output stream included in the Response object. Combine this information with the fact that the Servlet is the last stop in the Filter chain, and a problem starts to rear its ugly head. By the time the chain.doFilter()
method finishes, and control is returned to your Filter, the request and response objects have been passed through all downstream Filters and the Servlet, meaning the output stream is now closed, and cannot be written to. This is why the above solution throws an exception.
Thankfully, the Servlet API provides a standard way to deal with this issue, in the form of special Wrapper classes. Since we want to modify the response, we’ll use the HttpServletResponseWrapper
class (there is also a corresponding HttpServletRequestWrapper
as well). Since this is a wrapper, the class naturally extends HttpServletResponse
, so it can be used just like a regular response. Here’s how we’ll use it:
package net.cmw;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.PrintWriter;
/**
* Implementation of {@link HttpServletResponseWrapper} that returns
* its own output stream, thus protecting the wrapped response's stream.
*/
public class BasicResponseWrapper extends HttpServletResponseWrapper{
private CharArrayWriter outWriter;
/**
* Constructs a response adaptor wrapping the given response.
*
* @param response
* @throws IllegalArgumentException if the response is null
*/
public BasicResponseWrapper(HttpServletResponse response) {
super(response);
outWriter = new CharArrayWriter();
}
@Override
public String toString() {
return outWriter.toString();
}
@Override
public PrintWriter getWriter() throws IOException {
return new PrintWriter(outWriter);
}
}
The wrapper has two fields; the real response (which is required), and an output stream (in the form of a writer). We also override the class’ getWriter()
method so that it returns this output stream.
To use our class, we wrap a response object within it, and pass the wrapper to the downstream Servlet via doFilter()
. If the Servlet calls getWriter()
, it will get the wrapper’s output stream, rather than the response’s. This means that when control returns to our filter, we’ll still have access to the response’s stream (since the Servlet never touched it, and thus never closed it). We can read the contents of the wrapper’s stream, modify them as we see fit, and write the modified data to the response. Here’s the final version of the filter, demonstrating this in action:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
logger.info("Request incoming from " + request.getRemoteHost());
logger.info("Our test value is " + testValue);
// Wrap the response
HttpServletResponse wrappedResponse =
new BasicResponseWrapper( (HttpServletResponse)response);
// Pass in the wrapper
chain.doFilter(request, wrappedResponse);
// Grab the content from the wrapper's output
// stream
CharArrayWriter writer =
new CharArrayWriter();
writer.write(wrappedResponse.toString());
// modify the wrapper's content and write into the response
PrintWriter out = response.getWriter();
out.write("Our test value is " +
testValue + "\n");
out.write(writer.toString());
out.write("\nIsn't that great?");
}
To round out our example, let’s update the web.xml
file:
<?xml version="1.0" encoding="UTF-8"?>
<web-app
version="3.0"
xmlns:xsi="
http://www.w3.org/2001/XMLSchema-
instance"
xmlns="
http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-
app_3_0.xsd">
<display-name>
Basic Servlet Example
</display-name>
<filter>
<filter-name>LoggingFilter</filter-name>
<filter-class>net.cmw.LoggingFilter</filter-class>
<init-param>
<param-name>testValue</param-name>
<param-value>Test Value!</param-value>
</init-param>
</filter>
<!-- We'll only filter one of our two Servlets -->
<filter-mapping>
<filter-name>LoggingFilter</filter-name>
<url-pattern>/basic-filtered</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>Example Servlet</servlet-name>
<servlet-class>net.cmw.BasicServlet</servlet-class>
</servlet>
<!-- Unfiltered servlet -->
<servlet-mapping>
<servlet-name>Example Servlet</servlet-name>
<url-pattern>/basic</url-pattern>
</servlet-mapping>
<!-- Filtered servlet -->
<servlet-mapping>
<servlet-name>Example Servlet</servlet-name>
<url-pattern>/basic-filtered</url-pattern>
</servlet-mapping>
</web-app>
We’ll create two instances of BasicServlet
, each mapped to its own URL. One of the two will be filtered, and the other will not. If you run this final example and access both Servlets, you should see them return different responses.
What’s great about this example is that I can use this filter to with any other Servlet I write. And rather than write new code to enable this, I simply add a few more lines of configuration. This, I hope, is where you will see that splitting up our code into small, reusable chunks can sometimes make our lives a lot easier.
General Observations
- Until I wrote this post, I never knew that Filters could be used to modify the response. I don’t know whether this is because the practice isn’t recommended, or because developers simply aren’t aware of the feature. Either way, I’m glad to have discovered it. The more options we are aware of, the easier it is to make the right decision for a given situation. *On the flip side, I have use Filters to filter incoming requests. This is often done for security purposes, though I have seen it done for other reasons. Chances are good that you will find yourself having to write a Filter or two (or more!) in your career, so it’s good to know how they work. There is one last Servlet feature to cover, but before we get into it, the next post will have to make a detour to talk about Servlet configuration. It is boring, I know, but also important for the sake of comprehension.