3.8 Server-Side Caching

Java Servlet Programming, 2nd Edition > 3. The Servlet Lifecycle > 3.8 Server-Side Caching

 
< BACKCONTINUE >

3.8 Server-Side Caching

The getLastModified( ) method can be used, with a little trickery, to help manage a server-side cache of the servlet's output. Servlets implementing this trick can have their output caught and cached on the server side, then automatically resent to clients as appropriate according to the servlet's getLastModified( ) method. This can greatly speed servlet page generation, especially for servlets whose output takes a significant time to produce but changes only rarely, such as servlets that display database results.

To implement this server-side caching behavior, a servlet must:

  • Extend com.oreilly.servlet.CacheHttpServlet instead of HttpServlet

  • Implement a getLastModified(HttpServletRequest) method as usual

Example 3-10 shows a servlet taking advantage of CacheHttpServlet . It's a guestbook servlet that displays user-submitted comments. The servlet stores the user comments in memory as a Vector of GuestbookEntry objects. We'll see a version of this servlet running off a database in Chapter 9. For now, to simulate reading from a slow database, the display loop has a half-second delay per entry. As the entry list gets longer, the rendering of the page gets slower. However, because the servlet extends CacheHttpServlet, the rendering only has to occur during the first GET request after a new comment is added. All later GET requests send the cached response. Sample output is shown in Figure 3-4.

Example 3-10. A Guestbook Using CacheHttpServlet
import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; import com.oreilly.servlet.CacheHttpServlet; public class Guestbook extends CacheHttpServlet {   private Vector entries = new Vector();  // User entry list   private long lastModified = 0;          // Time last entry was added   // Display the current entries, then ask for a new entry   public void doGet(HttpServletRequest req, HttpServletResponse res)                                throws ServletException, IOException {     res.setContentType("text/html");     PrintWriter out = res.getWriter();     printHeader(out);     printForm(out);     printMessages(out);     printFooter(out);   }   // Add a new entry, then dispatch back to doGet()   public void doPost(HttpServletRequest req, HttpServletResponse res)                                 throws ServletException, IOException {     handleForm(req, res);     doGet(req, res);   }   private void printHeader(PrintWriter out) throws ServletException {     out.println("<HTML><HEAD><TITLE>Guestbook</TITLE></HEAD>");     out.println("<BODY>");   }   private void printForm(PrintWriter out) throws ServletException {     out.println("<FORM METHOD=POST>");  // posts to itself     out.println("<B>Please submit your feedback:</B><BR>");     out.println("Your name: <INPUT TYPE=TEXT NAME=name><BR>");     out.println("Your email: <INPUT TYPE=TEXT NAME=email><BR>");     out.println("Comment: <INPUT TYPE=TEXT SIZE=50 NAME=comment><BR>");     out.println("<INPUT TYPE=SUBMIT VALUE=\"Send Feedback\"><BR>");     out.println("</FORM>");     out.println("<HR>");   }   private void printMessages(PrintWriter out) throws ServletException {     String name, email, comment;     Enumeration e = entries.elements();     while (e.hasMoreElements()) {       GuestbookEntry entry = (GuestbookEntry) e.nextElement();       name = entry.name;       if (name == null) name = "Unknown user";       email = entry.email;       if (name == null) email = "Unknown email";       comment = entry.comment;       if (comment == null) comment = "No comment";       out.println("<DL>");       out.println("<DT><B>" + name + "</B> (" + email + ") says");       out.println("<DD><PRE>" + comment + "</PRE>");       out.println("</DL>");       // Sleep for half a second to simulate a slow data source       try { Thread.sleep(500); } catch (InterruptedException ignored) { }     }   }   private void printFooter(PrintWriter out) throws ServletException {     out.println("</BODY>");     out.println("</HTML>"); }   private void handleForm(HttpServletRequest req,                           HttpServletResponse res) {     GuestbookEntry entry = new GuestbookEntry();     entry.name = req.getParameter("name");     entry.email = req.getParameter("email");     entry.comment = req.getParameter("comment");     entries.addElement(entry);     // Make note we have a new last modified time     lastModified = System.currentTimeMillis();   }   public long getLastModified(HttpServletRequest req) {     return lastModified;   } } class GuestbookEntry {   public String name;   public String email;   public String comment; }
Figure 3-4. Guestbook output

The CacheHttpServlet source is shown in Example 3-11. The code is fairly complicated, especially at this point in the book. If you're interested, you may want to read the code for this class after completing Chapter 5.

Before handling a request, CacheHttpServlet class checks the value of getLastModified( ). 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 re-created. 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, or the getLastModified( ) method should take the headers and cookies into consideration. No caching is performed for POST requests.

CacheHttpServletResponse and CacheServletOutputStream are helper classes to the class and should not be used directly. The class has been built against Servlet API 2.2. Using it with previous Servlet API versions works fine; using it with future API versions likely won't work, because the HttpServletResponse interface that CacheHttpServletResponse must implement will probably change and leave some interface methods unimplemented. If you encounter such a problem, a current version of this class is always available at http://www.servlets.com.

It's interesting how CacheHttpServlet catches the request for early processing. It implements the service(HttpServletRequest, HttpServletResponse) method that the server calls to pass request-handling control to the servlet. The standard HttpServlet implementation of this method dispatches the request to the doGet( ), doPost( ), and other methods depending on the HTTP method of the request. CacheHttpServlet overrides that implementation and thus gains first control over the request handling. When the class has finished its processing, it can pass control back to the HttpServlet dispatch implementation with a super.service( ) call.

Example 3-11. The CacheHttpServlet Class
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;   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 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);   } }
 


Last updated on 3/20/2003
Java Servlet Programming, 2nd Edition, © 2001 O'Reilly

< BACKCONTINUE >


Java servlet programming
Java Servlet Programming (Java Series)
ISBN: 0596000405
EAN: 2147483647
Year: 2000
Pages: 223

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net