ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Another Java Servlet Filter Most Web Applications Should Have
Client-Side Cache Control

by Jayson Falkner
03/03/2004

There are several servlet filters that most web applications should have. A few of these filters are detailed in my previous article "Two Filters Every Web Application Should Have," but the theme to these types of filters is always the same — provide some type of functionality that will seriously help almost any web application. In the previous article, two filters were presented: a cache filter and a compression filter. In this article, we will code a filter that can modify HTTP response headers with the intention of using it to modify the client's web browser's cache. Client-side caching isn't as obvious as server-side caching, but it can be incredibly helpful, and it's near-trivial to implement.

Note: In this article, a nice user-friendly introduction to servlet filters is skipped, because you can find a perfectly good one with my previous article. Be sure you know what a servlet filter is before continuing, or else the code will make little sense.

HTTP Response Headers

The current HTTP specification is quite large and it is easy to only pay attention to the things that are forced upon you; e.g., you probably know what a URL is for index.jsp at www.jspbook.com (http://www.jspbook.com/index.jsp), but do you know how to type the HTTP request for the same resource? Did you know that a basic HTTP request is nothing more than plain text? Just for fun, here is what a basic HTTP 1.1 request looks like. Notice that it is typed using telnet.

# telnet www.jspbook.com 80
Trying 209.247.227.227...
Connected to www.jspbook.com.
Escape character is '^]'.
GET /index.jsp HTTP/1.1
host: foobar

After typing in the request (be sure to hit the Enter key an extra time), the web site www.jspbook.com returned the contents for /index.jsp, an HTML page.

HTTP/1.1 200 OK
Set-Cookie: JSESSIONID=285168DA4C5ACC1AF04EE3994F88BC15; Path=/
Content-Type: text/html
Transfer-Encoding: chunked
Date: Thu, 08 Jan 2004 03:12:26 GMT
Server: Apache-Coyote/1.1
2000
The HTML page's content was here but is skipped for brevity.

And that is how a web browser gets the content it uses to render a web page. Interesting if you haven't seen it before, but that is the greater point. If you haven't seen an HTTP request before, it is likely because you don't have to, when coding the average web application. The greater point being that the HTTP specification is full of all sorts of information, and the Servlet/JSP API doesn't necessarily reflect everything in the HTTP specification.

Some great examples of helpful things in the HTTP specification that new J2EE developers commonly don't know about are the HTTP headers that aren't abstracted by a method in either the javax.servlet.http.HttpServletRequest or the javax.servlet.http.HttpServletResponse class. Most developers know that an HTTP response can specify the length of its content and what type of content is being sent (i.e., MIME type). Both of these things correspond to HTTP response headers that are modified by the javax.servlet.http.HttpServletResponse object's setContentLength() and setContentType() methods, respectively — methods that directly abstract an HTTP response header. But what about all of the headers you can set using the HttpServletResponse object's setHeader() method? You'll have to look at the HTTP specification if you want to know these.

The following is the list of all the valid response headers as of HTTP 1.1.

You can click the above links to see what each response header is used for. We are going to specifically focus on the HTTP response header Cache-Control, which affects client-side content caching. Various uses of this header are helpful for most any web application, but it is not obvious that you can use them if you only look at the Servlet/JSP API documentation. Here is a key example: most web applications have a common graphic used on every page (say, your site's logo), and every request to your web site will require a user to download the common graphic. Why not have the client cache the graphic and save your server from having to transmit it for every single request? With the Cache-Control header you can do just this.

Caching Content in the Client's Browser

The Cache-Control HTTP header may be used to specify if and when a resource should be cached, and how long to consider a cache valid. Continuing the example above, imagine that we have a web site that uses the same logo (say, logo.png) on every page. In most cases, it makes a lot of sense to tell a user's browser that this graphic should be cached, which will hopefully benefit the user the next time they visit a page on your site. This is easily done by setting the Cache-Control header to have the value max-age=3600 in the HTTP response that transmits logo.png. The value public specifies that the content of this response is public information and that it should be cached by anything that can cache it. The value max-age=3600 specifies that the cache should be considered valid for 3600 seconds, which is 60*60 seconds, or an hour. For all requests up to an hour, unless the client forces the cache to be revalidated, the user's browser won't send a request to your server for logo.png; it will use the existing cached file. For one user, this is pretty neat -- the content-cached image will appear to have downloaded instantaneously. However, imagine the larger picture. If you use this technique as a standard practice, you can seriously cut down on the amount of HTTP requests hitting your server(s) just by making sure you are only sending a client the information they haven't already seen — imagine telling your boss you don't need that second server.

Hopefully, it should be clear that being able to manipulate HTTP headers is helpful; the Cache-Control header alone proves this point. The question now is, how do you do it? As mentioned earlier in the article, a simple servlet filter does the trick nicely. Here is the complete code for such a filter. The code is from the book support site for Servlets and JavaServer Pages; the J2EE Web Tier. You can also deploy a compiled version of the filter with your web app by putting jspbook.jar from http://www.jspbook.com/jspbook.jar in the WEB-INF/lib directory of your web application.

package com.jspbook;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
public class ResponseHeaderFilter implements Filter {
  FilterConfig fc;
  public void doFilter(ServletRequest req,
                       ServletResponse res,
                       FilterChain chain)
                       throws IOException,
                              ServletException {
    HttpServletResponse response =
      (HttpServletResponse) res;
    // set the provided HTTP response parameters
    for (Enumeration e=fc.getInitParameterNames();
        e.hasMoreElements();) {
      String headerName = (String)e.nextElement();
      response.addHeader(headerName,
                 fc.getInitParameter(headerName));
    }
    // pass the request/response on
    chain.doFilter(req, response);
  }
  public void init(FilterConfig filterConfig) {
    this.fc = filterConfig;
  }
  public void destroy() {
    this.fc = null;
  }
}

The only thing the above filter is doing is setting HTTP response headers to match whatever initial parameters that were provided for the filter. The four lines of code that accomplish this are the following.

for (Enumeration e=fc.getInitParameterNames();
     e.hasMoreElements();) {
   String headerName = (String)e.nextElement();
   response.addHeader(headerName,
                 fc.getInitParameter(headerName));
}

Aside from the above code, the rest of the filter is nothing more than a bare-minimum implementation of the javax.servlet.Filter interface.

To use the above filter in a helpful manner, you will have to set appropriate initial parameters to change the HTTP headers in which you are interested. The following deployment would change the Cache-Control header to match the earlier example. If you are trying the code, add these lines to your WEB-INF/web.xml file.

<filter>
  <filter-name>
   ResponseHeaderFilter</filter-name>
  <filter-class>
   com.jspbook.ResponseHeaderFilter</filter-class>
  <init-param>
    <param-name>
     Cache-Control</param-name>
    <param-value>
     max-age=3600</param-value>
  </init-param>
</filter>

<filter-mapping>
  <filter-name>
   ResponseHeaderFilter</filter-name>
  <url-pattern>/logo.png</url-pattern>
</filter-mapping>

A web application using the given code for the filter and the given deployment above would have clients cache the logo.png file, and the web application would not have to serve the image to clients who had already downloaded it within the last hour. The code can be tested by deploying it with a real web application and logging the HTTP requests (if you container doesn't log requests, you could code a filter to do so). Here is a simple demonstration using the latest release of Tomcat (5.0.16), which conveniently provides a method to dump HTTP request/response information. If you have Tomcat installed, uncomment out the following line in /conf/server.xml to enable the RequestDumperValve class.

<Valve
 className="org.apache.catalina.valves.RequestDumperValve"/>

With the RequestDumperValve enabled, a new file will appear in Tomcat's /logs directory, named catalina_log.[today's date].txt, where [today's date] is replaced with whatever the current date is. The contents of this file will include all of the information about HTTP requests and responses that Tomcat handles, including the HTTP headers involved.

Pages: 1, 2

Next Pagearrow