com.oreilly.servlet.CacheHttpServlet Maven / Gradle / Ivy
// 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 jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.WriteListener;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;
/**
* 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;
final 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 final 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 && !didRedirect;
}
private void internalSetHeader(String name, Object value) {
Vector