Supporting Conditional GET When Serving Plugins Downloadable Resources

Author Dan Hardiker
Website http://www.adaptavist.com/

One of the current problems with the plugins system is that resources served up don't have Last-Modified and ETag headers on the way out, nor do they listen to If-Modified-Since and If-None-Match headers on the way in. This means that the resources served can't be effectively cached by browsers.

Providing this capability is known as supporting HTTP Conditional GET - Charles has written about this in his blog.

Confluence supports conditional get natively for plugin resources in version 2.2. You will only need to apply this patch for Confluence 2.1.x and earlier.

Other areas of Confluence have these modification headers - so why not plugin resources?

Well it seems that the plugin system doesn't serve up the date the plugin was instantiated (the date the Plugin object was created in the running Confluence system). This means that working out a modification date is a little tricky as there is no way to know if the plugin has been updated. So first, the base class StaticPlugin in atlassian-plugins needs to be extended to add such a field. Secondly, the serving class PluginDownloadServlet in confluence will need to be extended to utilise the LastModifiedHandler class which is used elsewhere for modification header handling.

The following source modifications will result in a new atlassian-plugins jar and new PluginDownloadServlet class file you can replace in your installation. You will need:

  • Access to the Confluence source code - the modifications here are against version 2.1.4 of Confluence but may work for others
  • An environment setup to compile confluence - see Building Confluence From Source Code
  • An editor to make the changes

The classes we are interested in are:

Class Relative Source Location
com.atlassian.plugin.impl.StaticPlugin ./plugins/src/java/com/atlassian/plugin/impl/StaticPlugin.java
com.atlassian.confluence.servlet.download.PluginResourceDownload ./confluence/src/java/com/atlassian/confluence/servlet/download/PluginResourceDownload.java

1. Extend StaticPlugin to have a creationDate field

Add the field code below the list of field definitions at the top - this ended up being line 21 in Confluence 2.1.4:

private Date creationDate = new Date();

Add the getter code to the end of the class - this ended up starting at line 178 in Confluence 2.1.4:

public Date getCreationDate() {
    return creationDate;
}

You may need to add the an import for java.util.Date if your IDE doesn't do as much of your work as it should.

2. Compile the changes

Execute maven jar inside of ./plugins to have maven build and produce a target/atlassian-plugins-0.3.12.jar which can be copied over the existing one in WEB-INF/lib.

3. Extend PluginResourceDownload to have use the creationDate field to base it's modified header handling on.

Find the code below that actually serves the downloadable resource up - this ended up being line 105 in Confluence 2.1.4:

serveDownloadableResource(servlet, httpServletResponse, resource);

Add some code before it so that it now reads as follows - with the serveDownloadableResource(...) ending up on line 119 in Confluence 2.1.4:

// Check the modification
Plugin plugin = pluginManager.getPlugin(resource.getPluginKey());
// Find the last modified date
Date date = null;
if (plugin instanceof StaticPlugin) {
    StaticPlugin sPlugin = (StaticPlugin) plugin;
    date = sPlugin.getCreationDate();
}
// Run the last modified logic
LastModifiedHandler lastModifiedHandler = new LastModifiedHandler(date == null ? new Date() : date);
if (lastModifiedHandler.checkRequest(httpServletRequest, httpServletResponse)) {
    return;
}
// Serve the file
serveDownloadableResource(servlet, httpServletResponse, resource);
Confluence 2.1.5a Notes
The patch instructions are no longer up-to-date for 2.1.5a. You have to
modify the "servePluginResource" method in "PluginResourceDownload" so
that it accepts a "HttpServletRequest".

Best regards,
Thomas

4. Compile PluginResourceDownload

Execute maven inside of ./confluence to have maven build and produce a target/classes/com/atlassian/confluence/servlet/download/PluginResourceDownload.class which can be copied over the existing one in WEB-INF/classes/com/atlassian/confluence/servlet/download.

5. Restart Confluence

Restart Confluence and you should now see the modification headers with a HTTP "304 Not-Modified" status response if the plugin has not been updated since the last download of the plugin resource - and hopefully and marked improvement in speed!

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Mar 22, 2006

    Andrew Miller says:

    We'll have the academic license shortly for Confluence.....meaning no source cod...

    We'll have the academic license shortly for Confluence.....meaning no source code. Would someone mind compiling the files referenced above against Confluence 2.1.5 while we're waiting for the 2.2.x release?

    If possible, it would be much appreciated.

    1. Mar 22, 2006

      Guy Fraser says:

      The patch is definately included in Confluence 2.2 to be honest, it's probably b...

      The patch is definately included in Confluence 2.2 - to be honest, it's probably better waiting until you get 2.2 as that includes numerous other improvements for plugins.

      1. Mar 22, 2006

        Andrew Miller says:

        He he....I'll just sit on my hands and wait for 2.2 then. ;) Any timeline guesse...

        He he....I'll just sit on my hands and wait for 2.2 then. Any timeline guesses from what you've seen? (I won't hold you to them. )

        I'm hoping it will also reduce the large number of LDAP calls Confluence still seems to be making (though I need to dig up good debug info before I complain too much about that).

        1. Mar 22, 2006

          Guy Fraser says:

          Confluence 2.1.5a should solve the LDAP problems as it has far better caching, e...

          Confluence 2.1.5a should solve the LDAP problems as it has far better caching, etc.

          1. Mar 22, 2006

            Andrew Miller says:

            Quite true....I'm just still seeing a huge amount of ldap requests when I use tc...

            Quite true....I'm just still seeing a huge amount of ldap requests when I use tcpdump on the server running tomcat/Confluence.

            I'll be bringing up a standalone install (hopefully) soon so I can have Confluence as the only app on the box (at which point I'll know for sure all the ldap requests are from Confluence).

  2. Apr 14, 2006

    Guy Fraser says:

    The Conditional GET works great, however there is a slight issue with Firefox Fi...

    The Conditional GET works great, however there is a slight issue with Firefox – Firefox works properly! Firefox, having not been told that the resource will definately remain stable for a certain period of time or until a certain date, goes back to the server each time to check to see if it's been updated or not. Although the Conditional GET results in the server saying "the verion you have is fine, use your cached version", there is still lots of overhead caused when FF goes across the wire to check with the server.

    To help browsers such as Firefox give even better caching, could the following HTML header be added to the files served by the resources servlet?

    Cache-Control: max-age=3600, public

    That will tell FireFox (and many web proxies) to uber-cache the files for up to an hour from when they are first pulled - specifically it won't even check back with the server to see if the resource has changed, it just accepts that the one it already has is good for an hour.

    Admittedly, there are some minor glitches in Firefox so it will still occationally do a conditional GET, but having that extra header will at least ensure 50% of calls to the same resource within the space of an hour are pulled directly from cache without the browser even attempting to talk to the server.

    Withthis additional HTTP header, results on Firefox and a handful of other browsers should be just as fast as the results now given by the likes of MSIE with the patches to the resources servlet.

    If this is a simple enough entry for Confluence 2.2, it would be extremely appreciated

    1. Apr 15, 2006

      Dan Hardiker says:

      Adding cache headers is a different issue to this, and I'm not sure if I am in f...

      Adding cache headers is a different issue to this, and I'm not sure if I am in favour as it will mean that browsers will cache the output without consulting the server for a period of time.

      If it is implemented it must be simple to change the interval / switch off otherwise plugin development becomes a nightmare with having to remember to clear the cache between tests. Caching intermediary proxies would make clearing the cache manually impossible too.

      I would be in favour of this if resources defined in the atlassian-plugin.xml could have a "cache" attribute defining the max age. This way plugin developers can control how long their resources are cached for.

      1. Jun 06, 2006

        Guy Fraser says:

        That would certainly go a long way to dealing with this, however there's a need ...

        That would certainly go a long way to dealing with this, however there's a need to have them update if Confluence or the plugin is updated. Ideally the URL to them should contain the Confluence build number and the plugin version number so that if either change the resources get reloaded in order to ensure everything keeps running smoothly.

        1. Jun 07, 2006

          Dan Hardiker says:

          A messy hack (which is not typical of Atlassian) and breaks from the HTTP cachin...

          A messy hack (which is not typical of Atlassian) and breaks from the HTTP caching spec by coding to the effects, not the API - but it would work.