Servlets.com

Home

What's New?

com.oreilly.servlet

Servlet Polls

Mailing Lists

List Archives

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

Advertising Info

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   Archives
Engines   ISPs   Tools   Docs   Articles   Soapbox   Book

Copyright © 1999-2005 Jason Hunter
Privacy Policy

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