Writing Servlet Filters | The Filter Mapping Block

h2> The <filter-mapping> block

Now we need to tell the servlet container when to apply the filter, which is done with a <filter-mapping> block. The filter-mapping block includes the filter-name, which must match the name given in a <filter> block, and either a URL pattern or a servlet name. Most often you will use a URL pattern, but you can also specify the name of a servlet as defined in a <servlet> block.

URL patterns for filter-mappings are the same as for the <servlet-mapping> block, normally either a subpath such as "/secure/*", or a file ending like "*.gif". Exact paths may also be used, like "/filtered_page.html".

To have our filter handle all requests, we'll use the following filter-mapping block:

    <filter-mapping>
        <filter-name>Timer</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>    

This filter will run for every single request handled by the servlet engine. In most cases this would put an unnecessary load on the server, since few filters really need to execute for every image file as well as flat HTML, JSP, and servlet calls.

Using the FilterConfig object

When the servlet container starts it initializes each filter by calling the init() method, passing in a FilterConfig object. This object has two main uses, obtaining initialization parameters and accessing the container's ServletContext.

Initialization parameters are set in the web.xml file, in the <filter> block, and are accessed by calling FilterConfig.getInitParameter(). The names of all parameters can be retrieved with getInitParameterNames().

The ServletContext is useful for accessing data and services relating to the servlet container and the web application. For instance, it is generally better to send output to the container's log file rather than printing to standard output, as the TimerFilter code earlier in this article does. The original ExampleFilter code from the Tomcat project does this by saving a reference to FilterConfig in a class variable, and then gets the servlet context to call its log() method.

Let's look at the original Tomcat ExampleFilter code to see this in action. Not only does it use ServletContext, it also uses a configuration parameter to create a request attribute that could be retrieved by a servlet, JSP, or another filter. Although not obviously useful here, this is a handy technique for passing data between the components of a web application.

In its init() method the filter saves the FilterConfig object in an object variable, along with the "attribute" initialization parameter.

    private FilterConfig filterConfig = null;
    private String attribute = null;

    public void init(FilterConfig filterConfig) throws ServletException {

        this.filterConfig = filterConfig;
        this.attribute = filterConfig.getInitParameter("attribute");

    }

Then the doFilter() method uses the FilterConfig to grab the ServletContext and use its log() method to write to the log file. It also sets an attribute in the request named according to the init-parameter, whose value is the ExampleFilter object itself. This could be used in a JSP page or servlet, or a later Filter.

    
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain)
        throws IOException, ServletException {

        // Store ourselves as a request attribute (if requested)
        if (attribute != null)
            request.setAttribute(attribute, this);

        // Time and log the subsequent processing
        long startTime = System.currentTimeMillis();
        chain.doFilter(request, response);
        long stopTime = System.currentTimeMillis();
        filterConfig.getServletContext().log
            (this.toString() + ": " + (stopTime - startTime) +
             " milliseconds");

    }

Overriding the request or response

In most cases filters need to affect what happens later in the request process, even if later components or the resource being accessed don't know anything about the filter. A powerful way to do this is to substitute your own request or response object when calling the FilterChain.doFilter() method. The servlet specification offers special wrapper classes for this purpose, which can be subclassed to override their default behavior. The replacement class is instantiated passing in the original request or response object, so methods which aren't overridden can still be used.

This technique is particularly useful for filters that need to process the raw output of the request, for example encrypting, converting, or compressing the response data. Once again the Tomcat folks offer a good example of how to do this with CompressionFilter. Since most web browsers can automatically handle compressed pages, CompressionFilter compresses large pages on the fly. This can be applied not only to static HTML pages, but also HTML (or other format) output generated by servlets or JSP pages.

If CompressionFilter determines that the browser supports compression, it wraps the response with a CompressionServletResponseWrapper, which is a subclass of HttpServletResponseWrapper. Whatever final resource is accessed by the request will be sent to the browser by calling ServletResponse.getOutputStream() or ServletResponse.getWriter() to obtain a writer or stream to feed the output into. So the response wrapper overrides these methods, replacing the stream with a custom CompressionResponseStream that uses java.util.zip.GZIPOutputStream to compress whatever is written to it, or returning a writer which employs that same stream.

Here is the portion of CompressionFilter.doFilter() which actually wraps the response.

    if (response instanceof HttpServletResponse) {
        CompressionServletResponseWrapper wrappedResponse =
            new CompressionServletResponseWrapper((HttpServletResponse)response);
        wrappedResponse.setCompressionThreshold(compressionThreshold);
        if (debug > 0) {
            System.out.println("doFilter gets called with compression");
        }
        try {
            chain.doFilter(request, wrappedResponse);
        } finally {
            wrappedResponse.finishResponse();
        }
        return;
    }

The implementation of the custom output stream is actually more sophisticated than this. It waits to ensure the returned content is large enough to be worth compressing, and then uses the compression stream or a normal stream accordingly.

 1 
0 Comments  (click to add your comment)
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

About | Sitemap | Contact