Serving fast and effective static content
January 4, 2007 at 2:16 am Leave a comment
With AJAX becoming more and more popular most of us, developers, have to deal with static content like Javascript, CSS, IMG, XML etc… sooner or later. To improve the overall performance of the web application HTTP specification provide guidelines for caching the static content on the browser.
Caching is very good because the browser doesn’t have to query for the static content for every page request. Most popular browser like IE, FireFox, Safari, Opera first check their local cache for the static file/resource and only if they don’t find do they make a request to the webserver for the same.
With this arises a problem! What if I needs to update one of these static files? Can I force a browser to let go of the old cached instance of the same?
Well the answer is, the infamous, it depends
If a static file like javascript, css, img etc… is cached by a browser with no expiry time then there is no way to update the browser cache other than the brute force way(purge the browser cache or force the browser to refresh the cache). What it means is that there is no way that a web server can force the browser to update its local cache.
On the other hand if there was an expiration time associated with the static file then next time the same instance is requested by a page and if it is expired then the browser will request for the latest from the web server.
But having an expiration time for the static content is not always a preferred or an elegant solution especially with some of the new AJAX application where most of the development is in Javascript and also because its hard to predict how often the content will change. Although in most of the applications I dealt with, an expiration period of a day did very well (not to forget the amount of excess traffic we received every morning)
In this post I will explain how we can handle this in a better, elegant , efficient and effective way.
Before we delve into the details I thought its worth mentioning that there are other options for getting this issue resolved (each with its own advantages and disadvantages)
Here are a few of them
- Brute Force : CTRL-R (or F5) or whatever the keyboard mapping is to force refresh a page. Another brute fore way is to clear the browser cache every time there is a change. This is plain and simple but not practical in real world and I certainly don’t approve of this. Obviously we cannot ask each and every person using the web application to do this every time a new version is deployed.
- Force no-cache : In this approach Update the HTTP Headers to not cache anything (Cache-Control: no-cache). This is a crude way of fixing this problem. With AJAX becoming more and more popular these days the size and number of javascript files is increasing more than ever. So if every page request has to fetch all the static files it can affect the performance of the application.
- Expire Cache : Update the HTTP Headers to cache the files for only a certain period of time (Cache-Control: max-age= <in future>) after which the resource will expire and the browser is forced to fetch a new one.This approach also can get tricky based on what should the actual expiry time be like. Should the expiration be an hour, a day, a week or a month? Well it depends on the application and how often the file changes but still it will never be perfect. There will always be a window of opportunity where the file can be stale. Nevertheless this approach will suite most of the applications that don’t change very often.
- Versioning static files : By versioning what I mean is to add some kind of dynamic data to the static file names. Here are few ways of doing this
- Add timestamp to the file name. For example: instead of <script src=”js/prototype.js”> make it <script src=”js/prototype.js?12345455″>.
- Move all the static files into a sub folder with a number like “12/js/prototype.js” instead of just “js/protoype.js” and for every release (or deployment) bump the number up by one. This approach is good but there are couple of issues. One of which is that all the files under that directory need to be refreshed even if only one of then were changed. Another issue is that your app should be built in such a way that you can make changes like this without having to modify every file that includes these static files.
- The third way is to update the versions of only the files that were modified. We’ll talk about this in detail below.
Of all these approaches I like the 3rd approach in “Versioning static files”. Now how can we achieve this in J2EE world?
First, the idea is to move the logic of versioning away from all the JSP’s that include it. So everytime we change the version of a static file its reflected in all the including JSP’s. The best way to do this is by using custom tag libraries.
So lets start with a simple JSP (prior to versioning)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<%@ include file=“/common/taglibs.jsp”%>
<html>
<head>
<%@ include file=”/common/meta.jsp” %>
<title><fmt:message key=“webapp.name”/></title>
<link rel=“shortcut icon” href=“<c:url value=”/favicon.ico”/>”/>
<link rel=“stylesheet” type=“text/css” href="<c:url value="/css/style.css"/>"/>
<script type="text/javascript" src="<c:url value="/js/prototype.js"/>"></script>…
</head>
<body>
<div class="main">
</div>
</body>
</html>
To add versioning using custom tag library, the above should look like this
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<%@ include file=“/common/taglibs.jsp”%>
<%@ taglib prefix=”ut” uri=”/WEB-INF/utils.tld” %>
<html>
<head>
<%@ include file=”/common/meta.jsp” %>
<title><fmt:message key=“webapp.name”/></title>
<link rel=“shortcut icon” href=“<c:url value=”/favicon.ico”/>”/>
<ut:include type="css"><c:url value="/css/style.css"/></ut:include>…
<ut:include type="js"><c:url value="/js/js.js"/></ut:include>
</head>
<body>
<div class="main">
</div>
</body>
</html>
Here is the utils.tld
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
version="2.0">
<description>Versioning util library</description>
<display-name>versioning</display-name>
<tlib-version>1.1</tlib-version>
<short-name>ut</short-name>
<uri>http://www.example.org/taglibs/utils</uri>
<tag>
<name>include</name>
<tag-class>org.example.tags.IncludeTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>url</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>type</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>params</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
</taglib>
Now lets see the implementation of org.example.tags.IncludeTag
import java.io.IOException;import java.util.HashMap; import java.util.Map;import javax.servlet.jsp.tagext.BodyTagSupport; /** // Javascript versions // CSS Versions public String getUrl() { public void setUrl(String url) { public String getType() { public void setType(String type) { public String getParams() { public void setParams(String params) { pageContext.getOut().println(buf.toString()); /** return buf.toString(); /** |
So now everytime you have to make a new release just bump up the version numbers of all the files that have been changed and you no longer have to worry about stale or conflicting static files.
Resources
- http://www.thinkvitamin.com/features/webapps/serving-javascript-fast
- http://www.websiteoptimization.com/speed/tweak/cache/
- http://java.sun.com/products/jsp/tutorial/TagLibrariesTOC.html
Entry filed under: Caching, Dynamic Scripting, Performance, Tech. Tags: .

Trackback this post | Subscribe to the comments via RSS Feed