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

  1. 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.
  2. 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.
  3. 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.
  4. 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


package org.example.tags;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;import javax.servlet.jsp.tagext.BodyTagSupport;

/**
* Demonstrates how to use versioning for static content.
* In this example the static file and its version numbers are stored
* as key values pairs but in real-time it makes sense to have this information
* in a file (which probably is generated using a script). These version numbers
* can be random or can be the real version numbers from the source control.
*/
public class IncludeTag extends BodyTagSupport {
private String url = null;
private String type = null;
private String params = null;
private static final Map<String, String> versions = new HashMap<String, String>();

// Javascript versions
static {
versions.put(“/js/js.js”, “113″);
versions.put(“/js/prototype.js”, “159″);
}

// CSS Versions
static {
versions.put(“/css/style.css”, “158″);
versions.put(“/css/tabber.css”, “126″);
}

public String getUrl() {
return this.url;
}

public void setUrl(String url) {
this.url = url;
}

public String getType() {
return type;
//return getUrl().substring(getUrl().indexOf(“.”) + 1);
}

public void setType(String type) {
this.type = type;
}

public String getParams() {
return params;
}

public void setParams(String params) {
this.params = params;
} public int doEndTag() {
try {
if (bodyContent == null || bodyContent.getString() == null) {
url = “”;
}
else {
url = bodyContent.getString().trim();
}
StringBuffer buf = new StringBuffer();
final String type = getType();
if (type.equalsIgnoreCase(“js”)) {
buf.append(“<script type=\”text/javascript\” src=\”");
buf.append(getSource());
buf.append(“\”/></script>”);
}
else if(type.equalsIgnoreCase(“css”)) {
buf.append(“<link rel=\”stylesheet\” href=\”");
buf.append(getSource());
buf.append(“\” type=\”text/css\” media=\”screen, projection\”/>”);
}

pageContext.getOut().println(buf.toString());
} catch (IOException ignored) {}
return EVAL_PAGE;
}

/**
* Returns the source value that can be included in the script src
* attribute of css href attribute.
* @return
*/
protected String getSource() {
StringBuffer buf = new StringBuffer();
buf.append(getUrl());
String version = getVersion();
if (version != null) {
buf.append(“?v=” + version);
}
if (getParams() != null) {
if(version == null) {
buf.append(“?”);
}
else {
buf.append(“&”);
}
buf.append(getParams());
}

return buf.toString();
}

/**
* Returns the version number of this file
* @return
*/
protected String getVersion() {
return versions.get(getUrl());
}
}

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

  1. http://www.thinkvitamin.com/features/webapps/serving-javascript-fast
  2. http://www.websiteoptimization.com/speed/tweak/cache/
  3. http://java.sun.com/products/jsp/tutorial/TagLibrariesTOC.html
Advertisement

Entry filed under: Caching, Dynamic Scripting, Performance, Tech. Tags: .

Anemic Domain Model Illustrated One click resizing of browser window in MAC

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Trackback this post  |  Subscribe to the comments via RSS Feed


 

January 2007
M T W T F S S
    Apr »
1234567
891011121314
15161718192021
22232425262728
293031  

Flickr Photos

daydreams

More Photos

Follow

Get every new post delivered to your Inbox.