Servlets.com

Home

What's New?

com.oreilly.servlet

Servlet Polls

Mailing Lists

Servlet Engines

Servlet ISPs

Servlet Tools

Documentation

Online Articles

The Soapbox

"Java Servlet
Programming,
Second Edition"

"Java Enterprise
Best Practices"

Speaking & Slides

About Jason

XQuery Affiliate

Free Cache: Source code for CacheHttpServlet

The following is the source to the com.oreilly.servlet.CacheHttpServlet class, as written about in the soapbox article Free Cache: Come and Get It!


// Copyright (C) 1999 by Jason Hunter <jhunter@acm.org>.  All rights reserved.
// Use of this class is limited.  Please see the com.oreilly.servlet LICENSE 
// for more information.
//
// DO NOT CUT AND PASTE THIS CODE.  This class is available in source and
// compiled form from http://www.servlets.com/cos/.

package com.oreilly.servlet;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public abstract class CacheHttpServlet extends HttpServlet {

  CacheHttpServletResponse cacheResponse;
  long cacheLastMod = -1;
  String cacheQueryString = null;
  String cachePathInfo = null;
  String cacheServletPath = null;

  protected void service(HttpServletRequest req, HttpServletResponse res)
      throws ServletException, IOException {
    // Only do caching for GET requests
    String method = req.getMethod();
    if (!method.equals("GET")) {
      super.service(req, res);
      return;
    }

    // Check the last modified time for this servlet
    long servletLastMod = getLastModified(req);

    // A last modified of -1 means we shouldn't use any cache logic
    if (servletLastMod == -1) {
      super.service(req, res);
    }
    // If the client sent an If-Modified-Since header equal or after the 
    // servlet's last modified time, send a short "Not Modified" status code
    // Round down to the nearest second since client headers are in seconds
    else if ((servletLastMod / 1000 * 1000) <= 
             req.getDateHeader("If-Modified-Since")) {
      res.setStatus(res.SC_NOT_MODIFIED);
    }
    // Use the existing cache if it's current and valid
    else if (servletLastMod <= cacheLastMod && 
             cacheResponse.isValid() &&
             equal(cacheQueryString, req.getQueryString()) &&
             equal(cachePathInfo, req.getPathInfo()) &&
             equal(cacheServletPath, req.getServletPath())) {
      cacheResponse.writeTo(res);
    }
    // Otherwise make a new cache to capture the response
    else {
      cacheResponse = new CacheHttpServletResponse(res);
      cacheLastMod = servletLastMod;
      cacheQueryString = req.getQueryString();
      cachePathInfo = req.getPathInfo();
      cacheServletPath = req.getServletPath();
      super.service(req, cacheResponse);
    }
  }

  private boolean equal(String s1, String s2) {
    if (s1 == null && s2 == null) {
      return true;
    }
    else if (s1 == null || s2 == null) {
      return false;
    }
    else {
      return s1.equals(s2);
    }
  }
}

class CacheHttpServletResponse implements HttpServletResponse {
  // Store key response variables so they can be set later
  private int status;
  private Hashtable headers;
  private int contentLength;
  private String contentType;
  private Locale locale;
  private Vector cookies;
  private boolean didError;
  private boolean didRedirect;
  private boolean gotStream;
  private boolean gotWriter;

  private HttpServletResponse delegate;
  private CacheServletOutputStream out;
  private PrintWriter writer;

  CacheHttpServletResponse(HttpServletResponse res) {
    delegate = res;
    try {
      out = new CacheServletOutputStream(res.getOutputStream());
    }
    catch (IOException e) {
      System.out.println(
        "Got IOException constructing cached response: " + e.getMessage());
    }
    internalReset();
  }

  private void internalReset() {
    status = 200;
    headers = new Hashtable();
    contentLength = -1;
    contentType = null;
    locale = null;
    cookies = new Vector();
    didError = false;
    didRedirect = false;
    gotStream = false;
    gotWriter = false;
    out.getBuffer().reset();
  }

  public boolean isValid() {
    // We don't cache error pages or redirects
    return didError != true && didRedirect != true;
  }

  private void internalSetHeader(String name, Object value) {
    Vector v = new Vector();
    v.addElement(value);
    headers.put(name, v);
  }

  private void internalAddHeader(String name, Object value) {
    Vector v = (Vector) headers.get(name);
    if (v == null) {
      v = new Vector();
    }
    v.addElement(value);
    headers.put(name, v);
  }

  public void writeTo(HttpServletResponse res) {
    // Write status code
    res.setStatus(status);
    // Write convenience headers
    if (contentType != null) res.setContentType(contentType);
    if (locale != null) res.setLocale(locale);
    // Write cookies
    Enumeration enum = cookies.elements();
    while (enum.hasMoreElements()) {
      Cookie c = (Cookie) enum.nextElement();
      res.addCookie(c);
    }
    // Write standard headers
    enum = headers.keys();
    while (enum.hasMoreElements()) {
      String name = (String) enum.nextElement();
      Vector values = (Vector) headers.get(name); // may have multiple values
      Enumeration enum2 = values.elements();
      while (enum2.hasMoreElements()) {
        Object value = enum2.nextElement();
        if (value instanceof String) {
          res.setHeader(name, (String)value);
        }
        if (value instanceof Integer) {
          res.setIntHeader(name, ((Integer)value).intValue());
        }
        if (value instanceof Long) {
          res.setDateHeader(name, ((Long)value).longValue());
        }
      }
    }
    // Write content length
    res.setContentLength(out.getBuffer().size());
    // Write body
    try {
      out.getBuffer().writeTo(res.getOutputStream());
    }
    catch (IOException e) {
      System.out.println(
        "Got IOException writing cached response: " + e.getMessage());
    }
  }

  public ServletOutputStream getOutputStream() throws IOException {
    if (gotWriter) {
      throw new IllegalStateException(
        "Cannot get output stream after getting writer");
    }
    gotStream = true;
    return out;
  }

  public PrintWriter getWriter() throws UnsupportedEncodingException {
    if (gotStream) {
      throw new IllegalStateException(
        "Cannot get writer after getting output stream");
    }
    gotWriter = true;
    if (writer == null) {
      OutputStreamWriter w =
        new OutputStreamWriter(out, getCharacterEncoding());
      writer = new PrintWriter(w, true);  // autoflush is necessary
    }
    return writer;
  }

  public void setContentLength(int len) {
    delegate.setContentLength(len);
    // No need to save the length; we can calculate it later
  }

  public void setContentType(String type) {
    delegate.setContentType(type);
    contentType = type;
  }

  public String getCharacterEncoding() {
    return delegate.getCharacterEncoding();
  }

  public void setBufferSize(int size) throws IllegalStateException {
    delegate.setBufferSize(size);
  }

  public int getBufferSize() {
    return delegate.getBufferSize();
  }

  public void reset() throws IllegalStateException {
    delegate.reset();
    internalReset();
  }

  public boolean isCommitted() { 
    return delegate.isCommitted();
  }

  public void flushBuffer() throws IOException { 
    delegate.flushBuffer();
  }

  public void setLocale(Locale loc) { 
    delegate.setLocale(loc);
    locale = loc;
  }

  public Locale getLocale() { 
    return delegate.getLocale();
  }

  public void addCookie(Cookie cookie) { 
    delegate.addCookie(cookie);
    cookies.addElement(cookie);
  }

  public boolean containsHeader(String name) { 
    return delegate.containsHeader(name);
  }

  /** @deprecated */
  public void setStatus(int sc, String sm) { 
    delegate.setStatus(sc, sm);
    status = sc;
  }

  public void setStatus(int sc) { 
    delegate.setStatus(sc);
    status = sc;
  }

  public void setHeader(String name, String value) { 
    delegate.setHeader(name, value);
    internalSetHeader(name, value);
  }

  public void setIntHeader(String name, int value) { 
    delegate.setIntHeader(name, value);
    internalSetHeader(name, new Integer(value));
  }

  public void setDateHeader(String name, long date) { 
    delegate.setDateHeader(name, date);
    internalSetHeader(name, new Long(date));
  }

  public void sendError(int sc, String msg) throws IOException { 
    delegate.sendError(sc, msg);
    didError = true;
  }

  public void sendError(int sc) throws IOException { 
    delegate.sendError(sc);
    didError = true;
  }

  public void sendRedirect(String location) throws IOException { 
    delegate.sendRedirect(location);
    didRedirect = true;
  }

  public String encodeURL(String url) { 
    return delegate.encodeURL(url);
  }

  public String encodeRedirectURL(String url) { 
    return delegate.encodeRedirectURL(url);
  }

  public void addHeader(String name, String value) { 
    internalAddHeader(name, value);
  }

  public void addIntHeader(String name, int value) { 
    internalAddHeader(name, new Integer(value));
  }

  public void addDateHeader(String name, long value) { 
    internalAddHeader(name, new Long(value));
  }

  /** @deprecated */
  public String encodeUrl(String url) { 
    return this.encodeURL(url);
  }

  /** @deprecated */
  public String encodeRedirectUrl(String url) { 
    return this.encodeRedirectURL(url);
  }
}

class CacheServletOutputStream extends ServletOutputStream {

  ServletOutputStream delegate;
  ByteArrayOutputStream cache;

  CacheServletOutputStream(ServletOutputStream out) {
    delegate = out;
    cache = new ByteArrayOutputStream(4096);
  }

  public ByteArrayOutputStream getBuffer() {
    return cache;
  }

  public void write(int b) throws IOException {
    delegate.write(b);
    cache.write(b);
  }

  public void write(byte b[]) throws IOException {
    delegate.write(b);
    cache.write(b);
  }

  public void write(byte buf[], int offset, int len) throws IOException {
    delegate.write(buf, offset, len);
    cache.write(buf, offset, len);
  }
}

 


Home   com.oreilly.servlet   Polls   Lists   
Engines   ISPs   Tools   Docs   Articles   Soapbox   Book

Copyright © 1999-2005 Jason Hunter

webmaster@servlets.com
Last updated: March 1, 2009