Special thanks to our guest blogger, Dan Hardiker from Adaptavist.
Confluence in its naked, default form is nice, fast and responsive from the user’s perspective – but load it up with a few too many plugins and a nice looking theme, and you can soon find yourself waiting eight to ten seconds for a page to load.
The great developers at Yahoo! recently released YSlow, which works as a bolt-on for FireBug. This plugin analyses a webpage and compares the results to a set of performance enhancing rules that Yahoo recommends.
Now, there are a few of the rules (most notable the CDN rule) which aren’t applicable to most Confluence sites, but there is one I would like to focus on which does make a big difference: the expiry rule.
In a nutshell, the expiry rule recommends that for static resources which don’t change on a per request basis (e.g. non-dynamic images such as logos, glyphs, CSS, or Javascript libraries), we should then set the cache control timeout headers to the far distant future. This means the browser won’t even ask the server if the file has changed, saving large amounts of time in the HTTP Conditional Get cycle or, even worse, downloading of the entire file each time.
“But what if I upload a new version of the plugin, and the file has changed?” I hear you cry! “The browser will never ask for my new file!” Fear not, because the technique of making the browser play dumb and assume that a static resource URL hasn’t changed puts the control back into your hands. All you have to do is change the URL when your plugin is updated. The browser will see a new URL, think it’s a totally new resource, and then cache it in the same long-lived manner. The next time you update, change the URL again and repeat the process.
For example, in my plugin I have a library.js file which I would traditionally have served using /download/resources/my.plugin.key:any-module-key/resources/library.js. This would have had a corresponding <resource/> module in my atlassian-plugin.xml file. The response headers for this file when served up through the standalone environment are set to:

HTTP/1.x 200 OK
Server: Apache-Coyote/1.1
Expires: Sat, 01 Sep 2007 12:26:47 GMT
Last-Modified: Fri, 31 Aug 2007 15:48:44 GMT
Etag: "1188575324000"
Content-Type: application/x-javascript;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 01 Sep 2007 11:26:47 GMT

Note, there’s no Cache-Control header, and the Expires timeout is only an hour in the future! That’s not so hot, as this plugin might be running for months without modification. We would waste far too much time verifying that the resource hasn’t changed each hour. If your plugin (like Adaptavist’s Builder Theme) uses a lot of CSS, Javascript or static images then the effects of this can quite literally add seconds onto each and every page load.
What we want is to change it so that I’m actually pulling from /download/resources/my.plugin.key:any-module-key/HASH-GOES-HERE/resources/library.js, with the Cache-Control header set to a large max-age and set to private, and with the Expires header set to 10 years in the future or so. That hash just has to be unique, and I usually prefer to use the date and time (to the minute) that the plugin was enabled. This way, the hash changes automatically when the resource is likely to have changed, and remains the same (leaving the browser to safely assume that the files haven’t changed) when not.
There are three options for improving on the current caching situation:

  1. Change the <resource/> plugin module so that it allows this sort of thing transparently, perhaps configurably (so the hash can be set to change per module reload, plugin reload, Confluence restart or in fact never). This fix is not likely to appear immediately, and would require an upgrade of each Confluence system to reap the benefits.
  2. Add in your own cache header modifying servlet which looks at the pluginAccessor to get the information it needs to create the hash (a static variable is likely to work too). This requires a fair amount of implementation effort and would need to be duplicated in each plugin you want the effect to apply to.
  3. Use WebResourceManager! It already does it!

Yes – in the years that I have been using Confluence, at some point a funky caching filter has been added and I completely missed it!
If you have a macro or action that is being rendered by velocity, just change

<script type="text/javascript" src="$req.contextPath/download/resources/my.plugin.key:any-module-key/resources/library.js"></script>


<script type="text/javascript" src="$webResourceManager.getStaticPluginResource("my.plugin.key:any-module-key","resources/library.js")"></script>

If you aren’t using a macro or action (e.g. if you are outputting from a servlet plugin module) then you could alternatively, of course, get hold of the WebResourceManager component and call the method yourself.
When using the WebResourceManager you’ll find the script location changes to something like /s/808/1/1.0/_/download/resources/my.plugin.key:any-module-key/resources/library.js with the following headers:

HTTP/1.x 200 OK
Server: Apache-Coyote/1.1
Expires: Tue, 29 Aug 2017 11:29:15 GMT
Cache-Control: max-age=315360000000, private
Last-Modified: Sat, 01 Sep 2007 11:03:41 GMT
Etag: "1188644621000"
Content-Type: application/x-javascript;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 01 Sep 2007 11:29:15 GMT

Excellent! I’m off to go hunt down some of my plugins with large resources (like the Advanced Search Plugin, and the Atlassian Plugin Repository Client) and instantly make them faster to use.
I would highly recommend that you do too.

Dan Hardiker
Architect & Developer

Caching – Confluence Style