com.oreilly.servlet.CacheHttpServlet Maven / Gradle / Ivy
Show all versions of cos Show documentation
// Copyright (C) 1999-2001 by Jason Hunter .
// All rights reserved. Use of this class is limited.
// Please see the LICENSE for more information.
package com.oreilly.servlet;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
/**
* A superclass for HTTP servlets that wish to have their output
* cached and automatically resent as appropriate according to the
* servlet's getLastModified() method. To take advantage of this class,
* a servlet must:
*
* - Extend CacheHttpServlet instead of HttpServlet
*
- Implement a getLastModified(HttpServletRequest) method as usual
*
* This class uses the value returned by getLastModified() to manage
* an internal cache of the servlet's output. Before handling a request,
* this class checks the value of getLastModified(), and if the
* output cache is at least as current as the servlet's last modified time,
* the cached output is sent without calling the servlet's doGet()
* method.
*
* In order to be safe, if this class detects that the servlet's query
* string, extra path info, or servlet path has changed, the cache is
* invalidated and recreated. However, this class does not invalidate
* the cache based on differing request headers or cookies; for
* servlets that vary their output based on these values (i.e. a session
* tracking servlet) this class should probably not be used.
*
* No caching is performed for POST requests.
*
* CacheHttpServletResponse and CacheServletOutputStream
* are helper classes to this class and should not be used directly.
*
* This class has been built against Servlet API 2.2. Using it with previous
* Servlet API versions should work; using it with future API versions likely
* won't work.
*
* @author Jason Hunter, Copyright © 1999
* @version 0.93, 2004/06/25, added setCharacterEncoding() for servlets 2.4
* @version 0.92, 2000/03/16, added synchronization blocks to make thread safe
* @version 0.91, 1999/12/28, made support classes package protected
* @version 0.90, 1999/12/19
*/
public abstract class CacheHttpServlet extends HttpServlet {
CacheHttpServletResponse cacheResponse;
long cacheLastMod = -1;
String cacheQueryString = null;
String cachePathInfo = null;
String cacheServletPath = null;
Object lock = new Object();
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);
return;
}
// 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
if ((servletLastMod / 1000 * 1000) <=
req.getDateHeader("If-Modified-Since")) {
res.setStatus(res.SC_NOT_MODIFIED);
return;
}
// Use the existing cache if it's current and valid
CacheHttpServletResponse localResponseCopy = null;
synchronized (lock) {
if (servletLastMod <= cacheLastMod &&
cacheResponse.isValid() &&
equal(cacheQueryString, req.getQueryString()) &&
equal(cachePathInfo, req.getPathInfo()) &&
equal(cacheServletPath, req.getServletPath())) {
localResponseCopy = cacheResponse;
}
}
if (localResponseCopy != null) {
localResponseCopy.writeTo(res);
return;
}
// Otherwise make a new cache to capture the response
localResponseCopy = new CacheHttpServletResponse(res);
super.service(req, localResponseCopy);
synchronized (lock) {
cacheResponse = localResponseCopy;
cacheLastMod = servletLastMod;
cacheQueryString = req.getQueryString();
cachePathInfo = req.getPathInfo();
cacheServletPath = req.getServletPath();
}
}
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 String encoding;
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;
encoding = 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 (encoding != null) res.setCharacterEncoding(encoding);
if (locale != null) res.setLocale(locale);
// Write cookies
Enumeration enu = cookies.elements();
while (enu.hasMoreElements()) {
Cookie c = (Cookie) enu.nextElement();
res.addCookie(c);
}
// Write standard headers
enu = headers.keys();
while (enu.hasMoreElements()) {
String name = (String) enu.nextElement();
Vector values = (Vector) headers.get(name); // may have multiple values
Enumeration enu2 = values.elements();
while (enu2.hasMoreElements()) {
Object value = enu2.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
}
@Override
public void setContentLengthLong(long len) {
delegate.setContentLengthLong(len);
}
public void setContentType(String type) {
delegate.setContentType(type);
contentType = type;
}
public void setCharacterEncoding(String encoding) {
delegate.setCharacterEncoding(encoding);
encoding = encoding;
}
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 void resetBuffer() throws IllegalStateException {
delegate.resetBuffer();
contentLength = -1;
out.getBuffer().reset();
}
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);
}
public String getContentType() {
return delegate.getContentType();
}
/** @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);
}
@Override
public int getStatus() {
return delegate.getStatus();
}
@Override
public String getHeader(String name) {
return delegate.getHeader(name);
}
@Override
public Collection getHeaders(String name) {
return delegate.getHeaders(name);
}
@Override
public Collection getHeaderNames() {
return delegate.getHeaderNames();
}
}
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);
}
@Override
public boolean isReady() {
return delegate.isReady();
}
@Override
public void setWriteListener(WriteListener writeListener) {
delegate.setWriteListener(writeListener);
}
}