Discover freely available servlet
filters you can use today
(Originally published in JavaWorld, June 2001)
Summary
Jason Hunter looks in-depth at the new servlet filter model with an
examination of several freely available filters. You'll learn how these
filters work and what you can do with them. For a grand finale, Jason
describes his own multipart request filter that simplifies the handling of
file uploads. (3,200 words)
By Jason Hunter
In "Servlet 2.3: New Features Exposed," I
introduced the changes coming in the Servlet API 2.3 and gave a short tutorial
on the new servlet filter model. In this follow-on article, I'll dig deeper
into servlet filters and look at several filters you can download for free on
the Web. For each filter, I'll examine what it does, how it works, and where
you can get it.
You can use this article in two ways: to learn about some filters that are
useful out of the box, or as an aid in writing your own filters. I'll start
off with some simple examples and then move on to more advanced ones. At the
end, I'll show you a file upload filter that I wrote to support multipart
requests.
Servlet filters
In case you aren't yet familiar, a filter is an object that can transform a
request or modify a response. Filters are not servlets; they don't actually
create a response. They are preprocessors of the request before it reaches a
servlet, and/or postprocessors of the response leaving a servlet. As you'll
see later in the examples, a filter can:
- Intercept a servlet's invocation before the servlet is called
- Examine a request before a servlet is called
- Modify the request headers and request data by providing a customized
version of the request object that wraps the real request
- Modify the response headers and response data by providing a customized
version of the response object that wraps the real response
- Intercept a servlet's invocation after the servlet is called
You can configure a filter to act on a servlet or group of servlets. Zero or
more filters can filter one or more servlets. A filter implements
javax.servlet.Filter
and defines its three methods:
void init(FilterConfig config) throws ServletException
:
Called before the filter goes into service, and sets the filter's
configuration object
void destroy()
: Called after the filter has been taken out
of service
void doFilter(ServletRequest req, ServletResponse res, FilterChain
chain) throws IOException, ServletException
: Performs the actual
filtering work
The server calls init(FilterConfig)
once to prepare the filter
for service, then calls doFilter()
any number of times for
requests specially set up to use the filter. The FilterConfig
interface has methods to retrieve the filter's name, its init
parameters, and the active servlet context. The server calls
destroy()
to indicate that the filter is being taken out of
service. The filter lifecycle is now very similar to the servlet lifecycle --
a change recently made in the Servlet API 2.3 Public Final Draft #2.
Previously the lifecycle involved a setFilterConfig(FilterConfig)
method.
In its doFilter()
method, each filter receives the current
request and response, as well as a FilterChain
containing the
filters that still must be processed. In the doFilter()
method,
a filter may do what it wants with the request and response. (It could gather
data by calling their methods, or wrap the objects to give them new behavior,
as I'll discuss later.) The filter then calls chain.doFilter()
to transfer control to the next filter. When that call returns, a filter can,
at the end of its own doFilter()
method, perform additional work
on the response; for instance, it can log information about the response. If
the filter wants to halt the request processing and gain full control of the
response, it can intentionally not call the next filter.
Spot the slowdown
To really understand filters, you have to see them in action. The first
filter we'll look at is simple but powerful; it records the duration of all
requests. It's modeled loosely after the non-descriptively named
ExampleFilter
from the Tomcat 4.0 distribution. Here is the
code:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class TimerFilter implements Filter {
private FilterConfig config = null;
public void init(FilterConfig config) throws ServletException {
this.config = config;
}
public void destroy() {
config = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
long before = System.currentTimeMillis();
chain.doFilter(request, response);
long after = System.currentTimeMillis();
String name = "";
if (request instanceof HttpServletRequest) {
name = ((HttpServletRequest)request).getRequestURI();
}
config.getServletContext().log(name + ": " + (after - before) + "ms");
}
}
When the server calls init()
, the filter saves a reference to the
config in its config
variable, which is later used in the
doFilter()
method to retrieve the ServletContext
.
When the server calls doFilter()
, the filter times how long the
request handling takes and logs the time once processing has completed. This
filter nicely demonstrates before- and after-request processing. Notice that
the parameters to the doFilter()
method are not HTTP-aware
objects, so to call the HTTP-specific getRequestURI()
method
requires a cast of the request to an HttpServletRequest
type.
To use this filter, you must declare it in the web.xml
deployment
descriptor using the <filter>
tag, as shown below:
<filter>
<filter-name>timerFilter</filter-name>
<filter-class>TimerFilter</filter-class>
</filter>
This notifies the server that a filter named timerFilter
is
implemented in the TimerFilter
class. You can apply a registered
filter to certain URL patterns or servlet names using the
<filter-mapping>
tag:
<filter-mapping>
<filter-name>timerFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
This configures the filter to operate on all requests to the web application
(static or dynamic), just what we want for our timing filter. If you connect
to a simple page, the log output might look like this:
2001-05-25 00:14:11 /timer/index.html: 10ms
In Tomcat 4.0 beta5, you'll find the log file under
server_root/logs/
.
Who's on your site, and what are they doing?
Our next filter is a clickstream filter written by the folks at OpenSymphony. This filter tracks user requests (a.k.a.
clicks) and request sequences (a.k.a. clickstreams) to show a site
administrator who's visiting her site and what pages each visitor has accessed
so far. It's an open source library, under the LGPL license.
Inside the clickstream library you'll find a ClickstreamFilter
class that captures request information, a Clickstream
class that
operates like a struct to hold data, and a ClickstreamLogger
class that captures session and context events to glue everything together.
There's also a BotChecker
class that determines if a client is a
robot (using simple logic, like "Did they request robots.txt?"). To view the
data, the library provides a clickstreams.jsp
visitor summary
page and a supporting viewstream.jsp
visitor detail page.
We'll look first at the ClickstreamFilter
class. All these
examples are slightly modified from the original, for formatting and to fix
portability issues, which I'll discuss later.
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.*;
public class ClickstreamFilter implements Filter {
protected FilterConfig filterConfig;
private final static String FILTER_APPLIED = "_clickstream_filter_applied";
public void init(FilterConfig config) throws ServletException {
this.filterConfig = filterConfig;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// Ensure that filter is only applied once per request.
if (request.getAttribute(FILTER_APPLIED) == null) {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
HttpSession session = ((HttpServletRequest)request).getSession();
Clickstream stream = (Clickstream)session.getAttribute("clickstream");
stream.addRequest(((HttpServletRequest)request));
}
// pass the request on
chain.doFilter(request, response);
}
public void destroy() { }
}
The doFilter()
method gets the user session, obtains the
Clickstream
from the session, and adds the current request data
to the Clickstream
. It uses a special
FILTER_APPLIED
marker attribute to note if the filter was already
applied for this request (as might happen during request dispatching) and to
ignore any follow-on filtering action. You might be wondering how the filter
knows that the clickstream
attribute will be present in the
session. That's because the ClickstreamLogger
places it there
when the session is created. Here is the ClickstreamLogger
code:
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class ClickstreamLogger implements ServletContextListener,
HttpSessionListener {
Map clickstreams = new HashMap();
public ClickstreamLogger() { }
public void contextInitialized(ServletContextEvent sce) {
sce.getServletContext().setAttribute("clickstreams", clickstreams);
}
public void contextDestroyed(ServletContextEvent sce) {
sce.getServletContext().setAttribute("clickstreams", null);
}
public void sessionCreated(HttpSessionEvent hse) {
HttpSession session = hse.getSession();
Clickstream clickstream = new Clickstream();
session.setAttribute("clickstream", clickstream);
clickstreams.put(session.getId(), clickstream);
}
public void sessionDestroyed(HttpSessionEvent hse) {
HttpSession session = hse.getSession();
Clickstream stream = (Clickstream)session.getAttribute("clickstream");
clickstreams.remove(session.getId());
}
}
The logger receives application events and uses them to bind everything
together. On context creation, the logger places a shared map of streams into
the context. This allows the clickstreams.jsp
page to know what
streams are currently active. On context destruction, the logger removes the
map. When a new visitor creates a new session, the logger places a new
Clickstream
instance into the session and adds the
Clickstream
to the central map of streams. On session
destruction, the logger removes the stream from the central map.
The following web.xml
deployment descriptor snippet wires
everything together:
<filter>
<filter-name>clickstreamFilter</filter-name>
<filter-class>ClickstreamFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>clickstreamFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>clickstreamFilter</filter-name>
<url-pattern>*.html</url-pattern>
</filter-mapping>
<listener>
<listener-class>ClickstreamLogger</listener-class>
</listener>
This registers the ClickstreamFilter
and sets it up to handle
*.jsp
and *.html
requests. This also registers the
ClickstreamLogger
as a listener to receive application events
when they occur.
The two JSP pages pull the clickstream
data from the session and
context objects and use an HTML interface to display the current status. The
following clickstreams.jsp
file shows the overall summary:
<%@ page import="java.util.*" %>
<%@ page import="Clickstream" %>
<%
Map clickstreams = (Map)application.getAttribute("clickstreams");
String showbots = "false";
if (request.getParameter("showbots") != null) {
if (request.getParameter("showbots").equals("true"))
showbots = "true";
else if (request.getParameter("showbots").equals("both"))
showbots = "both";
}
%>
<font face="Verdana" size="-1">
<h1>All Clickstreams</h1>
<a href="clickstreams.jsp?showbots=false">No Bots</a> |
<a href="clickstreams.jsp?showbots=true">All Bots</a> |
<a href="clickstreams.jsp?showbots=both">Both</a> <p>
<% if (clickstreams.keySet().size() == 0) { %>
No clickstreams in progress
<% } %>
<%
Iterator it = clickstreams.keySet().iterator();
int count = 0;
while (it.hasNext()) {
String key = (String)it.next();
Clickstream stream = (Clickstream)clickstreams.get(key);
if (showbots.equals("false") && stream.isBot()) {
continue;
}
else if (showbots.equals("true") && !stream.isBot()) {
continue;
}
count++;
try {
%>
<%= count %>.
<a href="viewstream.jsp?sid=<%= key %>"><b>
<%= (stream.getHostname() != null && !stream.getHostname().equals("") ?
stream.getHostname() : "Stream") %>
</b></a> <font size="-1">[<%= stream.getStream().size() %> reqs]</font><br>
<%
}
catch (Exception e) {
%>
An error occurred - <%= e %><br>
<%
}
}
%>
The package is fairly easy to download and install from the OpenSymphony
website. Place and compile the Java files in WEB-INF/classes
,
put the JSP files in the Web application root, and modify the
web.xml
file as instructed. To save you the hassle of even this
much work, you can find a prepackaged WAR file available in Resources.
For the filter to work on Tomcat 4.0 beta5, I found I had to make some slight
portability modifications. The changes I made show some common pitfalls in
servlet and filter portability, so I'll list them here:
- I had to add an extra import line to the JSP files: <%@ page
import="Clickstream" %>. In Java you don't have to import classes within
your own package, so on servers where JSPs compile into the default package,
you don't need an import line like this. But on servers like Tomcat where
JSPs compile into a custom package, you have to explicitly import classes in
the default package.
- I had to move the <listener> element in the
web.xml
file after the <filter> and <filter-mapping> elements, as
required by the deployment descriptor DTD. Not all servers require elements
to be in the proper order, but Tomcat does.
- I had to change the
web.xml
mapping from /*.html
and /*.jsp
to the more correct *.html
and
*.jsp
. Some servers are forgiving of the leading slash, but
Tomcat rigidly enforces the rule that prohibits that slash.
- Finally, I brought the
ClickstreamFilter
class up to the
latest lifecycle API, changing setFilterConfig()
to the newer
init()
and destroy()
methods.
The downloadable WAR contains all these modifications and should run
out-of-the-box across servers, although I haven't tested it widely.
Squeeze the response
The third filter on our menu today automatically compresses the response
output stream, improving bandwidth utilization and providing a great
demonstration of response object wrapping. This filter is based on one
written by Amy Roh from Sun who contributed it to the Tomcat 4.0 "examples"
Web application. You'll find the original code under
webapps/examples/WEB-INF/classes/compressionFilters
. That filter
is available under the standard Apache license. The example code shown here
and in the WAR has been edited for clarity and simplicity.
The strategy of the CompressionFilter
class is to examine the
request headers to determine if the client supports compression, and if so,
wrap the response object with a custom response whose
getOutputStream()
and getWriter()
methods have been
customized to utilize a compressed output stream. Using filters allows such a
simple yet powerful solution.
Looking at the code, we'll start with the filter's init()
method:
public void init(FilterConfig filterConfig) {
config = filterConfig;
compressionThreshold = 0;
if (filterConfig != null) {
String str = filterConfig.getInitParameter("compressionThreshold");
if (str != null) {
compressionThreshold = Integer.parseInt(str);
}
else {
compressionThreshold = 0;
}
}
}
When called before the filter is put into service, this init()
method looks for the presence of a filter init
parameter to
determine the compression threshold -- the amount of bytes that must be in the
response before it's worth compressing.
The doFilter()
method, called when the request comes in,
retrieves the Accept-Encoding
header, and if the header value
includes gzip
, wraps the response and sets the threshold on the
wrapper:
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain ) throws IOException, ServletException {
boolean supportCompression = false;
if (request instanceof HttpServletRequest) {
Enumeration e = ((HttpServletRequest)request)
.getHeaders("Accept-Encoding");
while (e.hasMoreElements()) {
String name = (String)e.nextElement();
if (name.indexOf("gzip") != -1) {
supportCompression = true;
}
}
}
if (!supportCompression) {
chain.doFilter(request, response);
}
else {
if (response instanceof HttpServletResponse) {
CompressionResponseWrapper wrappedResponse =
new CompressionResponseWrapper((HttpServletResponse)response);
wrappedResponse.setCompressionThreshold(compressionThreshold);
chain.doFilter(request, wrappedResponse);
}
}
}
Notice how the request must be cast to an HttpServletRequest
before retrieving headers, just as with the first example. The filter uses
the wrapper class CompressionResponseWrapper
, a custom class
extending the standard HttpServletResponseWrapper
. The code for
the wrapper is relatively simple:
public class CompressionResponseWrapper extends HttpServletResponseWrapper {
protected ServletOutputStream stream = null;
protected PrintWriter writer = null;
protected int threshold = 0;
protected HttpServletResponse origResponse = null;
public CompressionResponseWrapper(HttpServletResponse response) {
super(response);
origResponse = response;
}
public void setCompressionThreshold(int threshold) {
this.threshold = threshold;
}
public ServletOutputStream createOutputStream() throws IOException {
return (new CompressionResponseStream(origResponse));
}
public ServletOutputStream getOutputStream() throws IOException {
if (writer != null) {
throw new IllegalStateException("getWriter() has already been " +
"called for this response");
}
if (stream == null) {
stream = createOutputStream();
}
((CompressionResponseStream) stream).setCommit(true);
((CompressionResponseStream) stream).setBuffer(threshold);
return stream;
}
public PrintWriter getWriter() throws IOException {
if (writer != null) {
return writer;
}
if (stream != null) {
throw new IllegalStateException("getOutputStream() has already " +
"been called for this response");
}
stream = createOutputStream();
((CompressionResponseStream) stream).setCommit(true);
((CompressionResponseStream) stream).setBuffer(threshold);
writer = new PrintWriter(stream);
return writer;
}
}
Any call to getOutputStream()
or getWriter()
returns
an object using CompressResponseStream
under the covers. The
CompressionResponseStream
class isn't shown in this example, but
it extends ServletOutputStream
and compresses the stream using
the java.util.zip.GZIPOutputStream
class.
The Tomcat "examples" Web application comes preconfigured with the compression
filter enabled with an example servlet set up. The example servlet responds to
the /CompressionTest
URL (make sure to prepend the
/examples
context path). With the WAR file I've made available,
you can access the test servlet at /servlet/compressionTest
(again, remember to prepend the appropriate context path). You can use the
following web.xml
snippet to configure the test.
<filter>
<filter-name>compressionFilter</filter-name>
<filter-class>CompressionFilter</filter-class>
<init-param>
<param-name>compressionThreshold</param-name>
<param-value>10</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>compressionFilter</filter-name>
<servlet-name>compressionTest</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>
compressionTest
</servlet-name>
<servlet-class>
CompressionTestServlet
</servlet-class>
</servlet>
The CompressionTestServlet
(not shown here) prints whether or not
the compression was possible, and when possible, announces the success with a
compressed response!
File upload filter
The final filter we'll look at handles multipart/form-data POST
requests, the type of request that can contain file uploads. Each
multipart/form-data POST
request contains any number of
parameters and files, using a special format not natively understood by
servlets. Servlet developers have historically used third-party classes to
handle the uploads, such as the MultipartRequest
and
MultipartParser
classes found in my own
com.oreilly.servlet
package. Here we see a new approach using a
MultipartFilter
to make the handling of such requests easier.
The filter builds on the parsers in the
com.oreilly.servlet
package and has been integrated into the
package. (See Resources.)
The MultipartFilter
works by watching incoming requests and when
it detects a file upload request (with the content type
multipart/form-data
), the filter wraps the request object with a
special request wrapper that knows how to parse the special content type
format. A servlet receiving the special request wrapper has seamless access
to the multipart parameters through the standard getParameter()
methods, because the wrapper has redefined the behavior of those methods. The
servlet can also handle uploaded files by casting the request to the wrapper
type and using the additional getFile()
methods on the wrapper.
Here is the code for the filter:
package com.oreilly.servlet;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class MultipartFilter implements Filter {
private FilterConfig config = null;
private String dir = null;
public void init(FilterConfig config) throws ServletException {
this.config = config;
// Determine the upload directory. First look for an uploadDir filter
// init parameter. Then look for the context tempdir.
dir = config.getInitParameter("uploadDir");
if (dir == null) {
File tempdir = (File) config.getServletContext()
.getAttribute("javax.servlet.context.tempdir");
if (tempdir != null) {
dir = tempdir.toString();
}
else {
throw new ServletException(
"MultipartFilter: No upload directory found: set an uploadDir " +
"init parameter or ensure the javax.servlet.context.tempdir " +
"directory is valid");
}
}
}
public void destroy() {
config = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String type = req.getHeader("Content-Type");
// If this is not a multipart/form-data request continue
if (type == null || !type.startsWith("multipart/form-data")) {
chain.doFilter(request, response);
}
else {
MultipartWrapper multi = new MultipartWrapper(req, dir);
chain.doFilter(multi, response);
}
}
}
The init()
method determines the file upload directory. This is
the location where the multipart parser places files, so that the entire
request doesn't have to reside in memory. It looks first for an
uploadDir
filter init
parameter, and failing to find
that, defaults to the tempdir
directory -- a standard context
attribute added in Servlet API 2.2.
The doFilter()
method examines the request content type, and
should it be a multipart/form-data
request, wraps the request
with a MultipartWrapper
. The wrapper code looks like this:
package com.oreilly.servlet;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class MultipartWrapper extends HttpServletRequestWrapper {
MultipartRequest mreq = null;
public MultipartWrapper(HttpServletRequest req, String dir)
throws IOException {
super(req);
mreq = new MultipartRequest(req, dir);
}
// Methods to replace HSR methods
public Enumeration getParameterNames() {
return mreq.getParameterNames();
}
public String getParameter(String name) {
return mreq.getParameter(name);
}
public String[] getParameterValues(String name) {
return mreq.getParameterValues(name);
}
public Map getParameterMap() {
Map map = new HashMap();
Enumeration enum = getParameterNames();
while (enum.hasMoreElements()) {
String name = (String) enum.nextElement();
map.put(name, mreq.getParameterValues(name));
}
return map;
}
// Methods only in MultipartRequest
public Enumeration getFileNames() {
return mreq.getFileNames();
}
public String getFilesystemName(String name) {
return mreq.getFilesystemName(name);
}
public String getContentType(String name) {
return mreq.getContentType(name);
}
public File getFile(String name) {
return mreq.getFile(name);
}
}
The wrapper constructs a com.oreilly.servlet.MultipartRequest
object to handle the upload parsing and overrides the
getParameter()
family of methods to use the
MultipartRequest
rather than the raw request to read parameter
values. The wrapper also defines various getFile()
methods so
that a servlet receiving this wrapped request can call additional methods to
handle the uploaded files.
The web.xml
deployment descriptor adds the filter like this:
<filter>
<filter-name>multipartFilter</filter-name>
<filter-class>com.oreilly.servlet.MultipartFilter</filter-class>
<!--
<init-param>
<param-name>uploadDir</param-name>
<param-value>/tmp</param-value>
</init-param>
-->
</filter>
<filter-mapping>
<filter-name>multipartFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>
uploadTest
</servlet-name>
<servlet-class>
UploadTest
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>
uploadTest
</servlet-name>
<url-pattern>
/uploadTest
</url-pattern>
</servlet-mapping>
The UploadTest
servlet looks like this:
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.oreilly.servlet.*;
public class UploadTest extends HttpServlet {
public void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.setContentType("text/html");
PrintWriter out = res.getWriter();
out.println("<HTML>");
out.println("<HEAD><TITLE>UploadTest</TITLE></HEAD>");
out.println("<BODY>");
out.println("<H1>UploadTest</H1>");
// Parameters can now be read the same way for both
// application/x-www-form-urlencoded and multipart/form-data requests!
out.println("<H3>Request Parameters:</H3><PRE>");
Enumeration enum = req.getParameterNames();
while (enum.hasMoreElements()) {
String name = (String) enum.nextElement();
String values[] = req.getParameterValues(name);
if (values != null) {
for (int i = 0; i < values.length; i++) {
out.println(name + " (" + i + "): " + values[i]);
}
}
}
out.println("</PRE>");
// Files can be read if the request class is MultipartWrapper
// Init params to MultipartWrapper control the upload handling
if (req instanceof MultipartWrapper) {
try {
// Cast the request to a MultipartWrapper
MultipartWrapper multi = (MultipartWrapper) req;
// Show which files we received
out.println("<H3>Files:</H3>");
out.println("<PRE>");
Enumeration files = multi.getFileNames();
while (files.hasMoreElements()) {
String name = (String)files.nextElement();
String filename = multi.getFilesystemName(name);
String type = multi.getContentType(name);
File f = multi.getFile(name);
out.println("name: " + name);
out.println("filename: " + filename);
out.println("type: " + type);
if (f != null) {
out.println("length: " + f.length());
}
out.println();
}
out.println("</PRE>");
}
catch (Exception e) {
out.println("<PRE>");
e.printStackTrace(out);
out.println("</PRE>");
}
}
out.println("</BODY></HTML>");
}
}
The first half of the servlet shows how the filter exposes parameter data
without any change to the receiving servlet. The second half shows how a
servlet can downcase the request to a MultipartWrapper
in
order to expose the additional file-access methods.
An example HTML form to drive this servlet is shown here:
<FORM ACTION="uploadTest" ENCTYPE="multipart/form-data" METHOD=POST>
What is your name? <INPUT TYPE=TEXT NAME=submitter> <BR>
What is your age? <INPUT TYPE=TEXT NAME=age> <BR>
Which file do you want to upload? <INPUT TYPE=FILE NAME=file1>
<BR>
Any other file to upload? <INPUT TYPE=FILE NAME=file2> <BR>
<INPUT TYPE=SUBMIT>
</FORM>
Here's some possible output:
UploadTest
Request Parameters:
submitter (0): Jason
age (0): 28
Files:
name: file1
filename: 4008b21.tif
type: application/octet-stream
length: 39396
name: file2
filename: null
type: null
Some of you may be wondering what assurance we have that the
MultipartWrapper
set by the filter will be directly
exposed to the follow-on servlet. In the specification Public Draft #2 and in
Tomcat 4.0 beta5, there is no guarantee. In fact, if you try to access the
servlet as /servlet/UploadTest
, you'll notice the filtering
doesn't work quite right, because the invoker handling /servlet
wraps the MultipartWrapper
with its own Tomcat-specific
wrapper. This allows the parameters to be parsed correctly, but the file
access methods won't be directly exposed. In discussing this issue with the
Servlet API expert group, we've decided that the servlet container shouldn't
do further wrapping beyond the filter's wrapper. The servlet specification
will be updated to make that clear. Later betas of Tomcat 4.0 will comply with
the clarified rules. A short-term workaround is to use the
getRequest()
method in the request wrapper to "walk up" the
request to find the shadowed multipart wrapper.
Filter power
Servlet filters provide a powerful ability to control request processing and
response generation, providing new functionality to servlets without large
amounts of servlet coding. I hope these filters have shown you some of what
is possible to do with filters, and taught you some tricks about how to make
the most effective use of the new filter mechanism.
My thanks to the authors of these filters and to the people who suggested
useful filters to write about: Amy Roh, Criag McClanahan, Serge Knystautas,
and the folks at OpenSymphony.
About the author
Jason Hunter is a senior technologist with CollabNet, a company that provides tools and
services for collaborative software development based on open source concepts.
He is author of Java Servlet Programming, 2nd Edition (O'Reilly,
April 2001), publisher of Servlets.com, and contributor to Apache Tomcat
(starting on the project when it was still Sun internal). He is also a member
of the expert groups responsible for Servlet/JSP and JAXP API development, and
holds a seat on the JCP Executive Committee overseeing the Java platform, as a
representative of the Apache Software Foundation. Most recently he co-created
the open source JDOM library to enable
optimized Java and XML integration.
To be notified when new articles are added to the site, subscribe here.