Producing CSS with self-caching JSPs

One thing that's always bugged me about CSS is that it doesn't have any sort of macro facility, so if you want to use (say) a HTML colour code in several places you need to hard-code it into all of them, which is a right pain. As the app I'm working on is written using JSPs, the obvious thing to do was to output the stylesheet from a JSP, and then use EL to define variables and then insert them in the appropriate places, e.g.:

<c:set var="background" value="#292B4F"/>

body {
    background-color:   ${background};
}

That works just fine, but each time the stylesheet is referenced it is regenerated, and as the content is completely static that was obviously a bit inefficient. Some googling found a number of ways to cache the output of JSPs, from the simple to the extremely complex, e.g. using a servlet filter to intercept requests and cache output. I wanted the simplest possible mechanism that would do the job, with no external dependencies.

A fairly obvious technique is to cache the output in the servlet's application scope the first time it is generated, then on subsequent requests you just send back the cached output rather than regenerating the content. The basic outline looks like this:

<c:if test="${empty cachedStylesheet}">
<c:set var="cachedStylesheet" scope="application">

    <!%-- Cached content goes here -->

</c:set>
${cachedStylesheet}

That works fine, but if you watch the conversation between the browser and the servlet, the cached content is still refetched each time this is referenced. This is because JSP output is usually dynamic, so the servlet container sets things up so that the client browser doesn't cache the content obtained from JSPs. To fix this we need to manually add the appropriate headers to the HTTP response we send back to the client to that it knows to cache the content, and we also need to respond to requests from the browser asking if the content has changed. The relevant HTTP headers are If-Modified-Since, Last-Modified and Expires, see the HTTP specification for more details. This requires a little bit of additional inline Java code in the JSP, so the final version looks like this:

<%@page contentType="text/css; charset=UTF-8" pageEncoding="UTF-8" session="false"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:if test="${empty cachedStylesheet}">
<c:set var="cachedStylesheet" scope="application">

<%-- Cached content goes here -->

</c:set>
<c:set var="cachedStylesheetDate"
  value="<%= new Long(new java.util.Date().getTime()) %>"
  scope="application"/>
</c:if>
<%
  long date = (Long) application.getAttribute("cachedStylesheetDate");
  long mod = request.getDateHeader("If-Modified-Since");
  if (mod > date) {
      response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
      return;
  }
  response.setDateHeader("Last-Modified", date);
  response.setDateHeader("Expires", new java.util.Date().getTime() + 86400000);
%>
${cachedStylesheet}

Here's how it works: We cache the content the first time the JSP is accessed as before, but we also set a session scope variable cachedStylesheetDate that records the time when the output was generated. Then on each request we fetch the value of any If-Modified-Since header specified by the browser. If the content was generated before the date, the browser already has an up-to-date version of the content, so we just send back a 304 response (Not Modified) to indicate that fact. Otherwise we set the Last-Modified header to the date when the content was generated, and then set the Expires header to tell the browser to check again in (60 * 60 * 24) * 1000 = 86400000 milliseconds, i.e. in 24 hours, and then we send the cached output. That way, after the initial fetch, the browser will only check once a day to see if the content has been updated - that figure can of course be adjusted as necessary.

Tags : , , , ,
Categories : Web, Tech