HTML5 App-Cache can be a boon for developers of Web Applications, provided you understand how it’s meant to be used, and provided you stick to the happy path. Before we look at App-Cache itself, it’s worth first reminding ourselves what the best practices are for HTML4 Web Apps…
Traditional HTTP Caching (The Simple Way)
At a minimum, the resources used by an App should have one of the following two caching headers associated with them:
- A
Last-Modified
header that provides a timestamp of the resource modification time. - An
ETag
header specifying a unique version identifier that is guaranteed to change each time the resource changes.
Either of these two headers enables the browser to conditionally load all resources, only downloading a fresh copy of a given resource when that resource has actually changed since the last visit. Since a Last-Modified
header is almost always provided automatically by the web server, there is generally nothing for the developer to do. So far so good.
Why The Simple Way Isn’t Enough Without HTTP Pipelining
Now, if it wasn’t for the fact that HTTP Pipelining can’t be enabled within browsers by default — due to some naughty proxies that claim to support HTTP/1.1, but don’t — then the traditional caching would be sufficient, and there’d be nothing more to say at this point.
However, given that pipelining can’t be used, and that we have to perform a round trip connection over potentially latent lines for each resource we require, more cunning is required if we want large apps to start quickly.
By The Way: I’ve long thought that the IETF should release an HTTP/1.2 spec that says “See HTTP/1.1, but this time when say that HTTP Pipelining is mandatory, we really mean it!”.
Traditional HTTP Caching (The Comprehensive Way)
Adding a max-age
directive to our caching header will enable browsers to skip the conditional GET
entirely. Unfortunately, putting expiry headers on resources willy-nilly will end up preventing the user from getting timely updates, or any updates at all! Worse still, users can also end up with inconsistently versioned resources, and thus badly broken apps.
The only strategy that really works is to version your resource URLs. This allows your resources to be cached indefinitely since you will merely nudge the version number on each release. The simplest way to do this is to use the HTML <base/>
tag to do the heavy lifting for you, by placing the following in your page’s <header/>
section:
<base href="http://app.acme.com/v/2.4/"/>
This tells the browser to act as though the page you requested from http://app.acme.com/
actually came from http://app.acme.com/v/2.4/
, and so all resource requests will have this version prefix automatically prepended to them. This, paired with a URL re-mapping strategy on your server to strip this version information of the URLs before they’re processed is all you need. Neat!
The App-Cache Way
Once you slap a cache manifest on your application (please see the resources section at the end of this article for details about how to do this), things become subtly different. One immediate side effect is that you will no longer need to version your resources or set expiry headers for them, which is really nice. Here are some of the main gotchas to be aware of though:
- The initial population or updating of the cache contents is always done in the background, leaving your app largely unaffected in how it loads.
- The initial load of your application will still start start showing resources as they become available, and will not delay the starting of your app while the cache is populated; you may want to blank the screen and show a progress bar during this period.
- Subsequent loads of your application will start immediately (running from cache); if there are updates available (determined by seeing if the manifest has changed) then any modified resources will be loaded in the background, and will not be immediately available within the running application.
Apart from potentially showing a progress bar during the initial cache population, you may also want to consider what you do once a background update has completed. There are 3 strategies you can adopt here:
- Do nothing, leaving the user to begin using the update the next time they load they app.
- Inform the user that a new version is available, providing them with a button that enables them to reload the application if they want to use the new version (via
window.location.reload()
). - Dynamically switch in the new cache by invoking the
window.applicationCache.swapCache()
method, and then … ???
This third option still baffles me really. While switching the cache will cause simple resources to switch over, your Javascript will remain untouched, and so your app will now be in an inconsistent state. You could implement a cunning strategy where you load the new classes in, and switch the prototypes of all your objects to use the new classes, but this would only work if the member variables were all the same! There must be some benefit to this method, but I’ve yet to fathom what it is — if you know the answer then please do add a comment.
Another issue I ran into was the fact the event life-cycle doesn’t seem to inform you whether this is the initial cache population (effectively an ‘install’) or a subsequent cache population (effectively an ‘update’), until after the process has finished. You can see this for yourself by looking at the event life-cycle diagram below. Here, the ‘cached’ event signifies that this was an ‘install’, whereas the ‘updateready’ event signifies that this was an ‘update’: I ended up using Local Storage (another HTML5 feature) to help with this.
Disabling AppCache In Development
Another problem you’ll face once you start using a cache manifest is that modifying your app will become much more complicated. In fact, you will only see your changes if you remember to both:
- Update the manifest file.
- Hit the reload button … twice!
An easy way to get around this is to configure the web server in your development environment to serve the manifest with the wrong Content-Type MIME header, causing it to be ignored by the browser. You can do this as soon as any App-Cache specific code you choose to write becomes stable.
Apache Server, Firefox & Transparent Proxies
The App-Cache specification advocates that web browsers ignore any caching headers set on the manifest. This, IMO, is probably mis-guided — it advocates Postel’s Law in preference to Fail Fast. And, given that people will be connecting to your site through ISP and Instituional proxies that take these caching headers seriously, you may end up finding that users in the field are unable to receive updates if you’ve accidentally set long expiry headers on the cache manifest.
Some servers (for example Apache) even set expiry headers for static resources by default, unless you tell it not to. Fortunately, Firefox prefers to follow the spirit of the HTTP specification and obey the caching headers your server sends, rather than merely ignoring them as suggested by the App-Cache specification, and so you are more likely to catch this problem early, before you lose the ability to update your Web App in the wild.
Conclusion
Using App-Cache has lot’s of benefits compared with the previous HTML4 way of doing things, once you properly understand how it works, and provided you disable it within your development environment. Hopefully this guide will more quickly help you to become productive with App-Cache than I was able to.
Resources
All of these articles are good resources regarding the cache manifest file itself:
My web app works perfectly on all browsers except Firefox. Using jQuery I have a confirm dialog that pops up when a change to the .manifest file has been made asking the user if they want to apply the new updates. When you click okay to accept the updates I have these two lines of code:
window.applicationCache.swapCache();
window.location.reload();
Which if Firefox do nothing. None of the changes I make to my site take affect. For the life of me I cannot figure out what I am doing wrong. Any idea’s?
It doesn’t really make sense to invoke
swapCache()
if you are going to reload the entire page anyway, and in fact this may actually be the problem. When I originally wrote the article I couldn’t even see a point for that method because it didn’t affect objects you’d already constructed, but perhaps Firefox has been updated to re-run all code from scratch, so that your second line of code never gets invoked?Just a guess really. I haven’t done anything with App-Cache in quite some time.