View Source

The purpose of this page is to describe one strategy for caching of static resources in a JSP based web application.
The term "_assets_" is used to denote static files javascript, CSS, images, etc.

Credit go to [Trygve Laugstøl|https://twitter.com/trygvis] and [Erlend Hamnaberg|https://twitter.com/hamnis] for describing the concepts and suggesting implementations.


h4. What

# External assets should be cached forever.
# Internal images should be cached forever.
# Internal JavaScript and CSS files should be reloaded for every new release.
# Minify JavaScript and CSS

h4. Abstract implementation steps

# Ensure internal and external assets are put in separate folders.
# Ensure all external assets are versioned
## version in folder OR
## version in filename
# Ensure internal images are versioned if changed.
# Use [auto-versioning|http://derek.io/blog/2009/auto-versioning-javascript-and-css-files/] for internal assets.
# Set Cache-Control headers
# Remove ETags and Last-Modified headers (to increase chance of browsers have the same caching behaviour)
# Minify JavaScript and CSS build time

h4. 4. and 5. - Auto-version based on Tucket UrlRewriteFilter

h6. pom.xml

{code:xml}

<dependency>
<groupId>org.tuckey</groupId>
<artifactId>urlrewritefilter</artifactId>
<version>4.0.4</version>
</dependency>

<properties>
<!-- http://rterp.wordpress.com/2012/03/16/stamping-version-number-and-build-time-in-properties-file-with-maven/ -->
<maven.build.timestamp.format>yyyyMMddHHmmss</maven.build.timestamp.format>
<buildTime>${maven.build.timestamp}</buildTime>
</properties>
{code}

h6. Intermediate property file

{code}
version=${project.version}
buildTime=${buildTime}
{code}


h6. ServletFilter to set session attribute

{code:java}
public class ProjectVersionFilter implements Filter {
public static final String WEBAPP_VERSION = "webappversion";

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
HttpSession session = ((HttpServletRequest)request).getSession();
String webappVersion = (String) session.getAttribute(WEBAPP_VERSION);
if (webappVersion == null || webappVersion.isEmpty()) {
session.setAttribute(WEBAPP_VERSION, getTimestamp());
}

filterChain.doFilter(request, response);
}

/**
* Dateformat must match dateformat specified in pom.xml.
*/
private String getTimestamp() {
String version = releaseConfig.getString("version", "");
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
String now = df.format(new Date());
if (version.contains("SNAPSHOT")) {
return now;
} else {
String buildTime = releaseConfig.getString("buildTime", "");
if (buildTime.isEmpty()) {
log.warn("buildTime property not set, use timestamp now. Consequence: A restart will force clients to reload static resources.");
}
return buildTime;
}
}
}
{code}

http://www.tuckey.org/urlrewrite/


h6. In WEB-INF/urlrewrite.xml:
{code:xml}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 4.0//EN" "http://www.tuckey.org/res/dtds/urlrewrite4.0.dtd">

<urlrewrite>
<!-- Fetch webappversion attribute from session and add to url used in the JSP. -->
<outbound-rule>
<from>^/(css|js)/(.*)$</from>
<to>%{context-path}/%{session-attribute:webappversion}/$1/$2</to>
</outbound-rule>

<!-- Example: Requests for http://localhost/20130130074159/js/scriptA.js will be forwarded to http://localhost/js/scriptA.js. -->
<rule>
<from>^/\d{14}/(css|js)/(.*)$</from>
<to>/$1/$2</to>
</rule>


<!-- Cache internal resources for 100 days, but only in browser cache. -->
<rule>
<from>^/(css|js)/(.*)$</from>
<set type="response-header" name="Cache-Control">max-age=8640000, private</set>
</rule>

<!-- Cache external resources for 100 days. -->
<rule>
<from>^/(ico|img|jslibs)/(.*)$</from>
<set type="response-header" name="Cache-Control">max-age=8640000, public</set>
</rule>
</urlrewrite>
{code}


h6. web.xml

{code:xml}
<filter>
<filter-name>ProjectVersionFilter</filter-name>
<filter-class>com.company.app.core.web.ProjectVersionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ProjectVersionFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
<filter-name>urlRewriteFilter</filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
<init-param>
<param-name>logLevel</param-name>
<param-value>LOG4J</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>urlRewriteFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
{code}

h6. Script tag in JSP file

Note the url encoding using c:url.

{code}
<script src="<c:url value="/js/scriptA.js"/>"></script>
{code}


h4. Resources

http://derek.io/blog/2009/auto-versioning-javascript-and-css-files/

Good guide to web caching: http://www.mnot.net/cache_docs/

http://stackoverflow.com/questions/2630885/auto-versioning-of-static-content-with-jboss