Wednesday, February 28, 2007

Tomcat Cache Control Customization

In the last post Customize Tomcat's Static File Serving we discussed how to customize file serving in our application.

If we allow Tomcat DefaultServlet to serve the static resource, it sends back ETag header which is used for cache control on the client browser. The problem with this is client (browser) will send request to check if the file is modified each time we refresh the page. This will add overhead to our application as well as server.

We might want to completely avoid client browser (if cache is enabled) from requesting a resource until sometime, in such case right cache control headers should be sent back. Here is our new CustomFileServlet which add custom cache headers if the resource served is javascript or css file and such resource will asked to cache for a day.

package com.example.servlet;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.servlets.DefaultServlet;

public class CustomFileServlet extends DefaultServlet {
private static final long serialVersionUID = 1L;
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
if (processRequest(request, response)) {
super.doPost(request, response);
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
if (processRequest(request, response)) {
super.doGet(request, response);
public boolean processRequest(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
String requestURI = request.getRequestURI();
String contextPath = request.getContextPath();

String fileURI = "";
if (requestURI.indexOf(contextPath) != -1) {
fileURI = requestURI.substring(
+ contextPath.length());

File file = new File(request.getSession()
String lowerfile = file.getAbsolutePath().toLowerCase();

boolean isBasePath = "/".equalsIgnoreCase(fileURI);
boolean isJSFile = lowerfile.endsWith(".js");
boolean isCSSFile = lowerfile.endsWith(".css");
boolean customServing = (!isBasePath) && (isJSFile || isCSSFile);

if(customServing) {
String mimetype = request.getSession()

// Required Cache Control Headers
String maxage = "86400"; // One day in Seconds
response.setHeader("Cache-Control", "max-age="+ maxage);
long relExpiresInMillis = System.currentTimeMillis() + (1000 * Long.parseLong(maxage));
response.setHeader("Expires", getGMTTimeString(relExpiresInMillis));
response.setHeader("Last-Modified", getGMTTimeString(file.lastModified()));

// Serve the file content
FileInputStream fis = new FileInputStream(file);
OutputStream ostream = response.getOutputStream();
streamIO(fis, ostream);

return false; // We have taken care of file serving.
return true; // Let DefaultServlet handle file serving
public static String getGMTTimeString(long milliSeconds) {
SimpleDateFormat sdf = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'");
return sdf.format(new Date(milliSeconds));
public static void streamIO(InputStream is, OutputStream os)
throws IOException {
byte[] bytes = new byte[2048];
int readlen = -1;
while ((readlen = != -1) {
os.write(bytes, 0, readlen);
os.flush(); // Let us flush after bluk write

Comments: Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?