Prevent Browser Caching of Dynamic Content | WebReference

Prevent Browser Caching of Dynamic Content

By Rob Gravelle


Just when you thought that Ajax was paving the way to a richer and more interactive browsing experience, it has run into some resistance from proponents of the Web 2.0 paradigm. In a nutshell, Web 2.0 is about making websites machine-readable so that content can be shared seamlessly between unrelated sites. The rich client goal of Ajax-based frameworks runs contrary to the Web 2.0 ideals in many ways. Web developers who now find themselves in the crossfire. So what's a developer to do? Stick with straight HTML, CSS, and a bit of JavaScript to enable easy data sharing or use Flash to emulate a desktop application - thus fostering a richer client experience? Personally, I am all for ease of data sharing, but I believe that the Web should be about people first. Today we'll be discussing the use of Ajax in client-side caching. It's really a two pronged topic: first, it is beneficial for performance to cache the data once it's been downloaded because it minimizes server calls; second, you have to be able to force the browser to refresh some content when a server call is made. We tackled the first issue in Building a Client-Side Ajax Cache. This article will concentrate on the second one concerning content refreshing.

Before we get started, you may want to brush up on the basics of Ajax, since this article assumes that you already have a working knowledge of it.

Using Ajax to Refresh Dynamic Content

A typical usage for Ajax is to refresh a portion of the page at a set interval. For example, imagine a page that contains the current weather conditions. The latest conditions should be updated every so often, say every five minutes. The Prototype Framework has just the thing: it's called the PeriodicalUpdater. All we need to do is supply it with the ID of the page element that we want to update, the URL of the server resource, and a JavaScript object literal containing some optional parameters. We need to supply the frequency because it differs from the default of every two seconds. We'll attach it to the window's onload event using the Event.observe() method. It takes the object, the event name (without the 'on'), and the function to bind to it:

Pretty easy, so far. The trouble is that different browsers implement caching in different ways. Internet Explorer, always the black sheep of browsers, caches dynamic pages, in addition to static ones. This is a problem because, although the URL of the page may not change, the content will. This is especially true in the case of partial page updates. One work around for this situation is to add a unique time stamp and/or random number, to the end of the URL. This is achieved using the Date object and/or Math.random(). Here is our PeriodicalUpdater with a random number ID appended as a query parameter called sid:

This will prevent the page from being cached, but there is a downside in that each request will now be stored in the cache, adding useless and never reused content that could be better utilized for more static pages. Not to mention, more useful data will be purged from cache to make way for these one-time responses.

A less obtrusive solution is to set the server call's request headers in such a way as to prevent caching of the dynamic content. The idea is basically to trick the browser into thinking that the content has expired so that a trip to the server is necessary. Most browsers implement some sort of caching, but there are differences in how and when the cached data is revalidated. For instance, Firefox revalidates the cached response every time the page is refreshed, issuing an If-Modified-Since header with value set to the value of the Last-Modified header of the cached response. Internet Explorer, on the other hand, does so only if the cached response is expired (I.E., after the date of received Expires header). The following set of headers should work on all major browsers:

In the above example, we modified the previous Ajax call to include the relevant request headers. Prototype allows you to include custom HTTP request headers using the requestHeaders option. It accepts name-value pairs as a hash (as in our example) or in a flattened array, like: ['X-Custom-1', 'value', 'X-Custom-2', 'other value'].

A Brief Lesson on Request Header Fields

As per the HTTP protocol, a request message may contain one or more header fields, which are formatted one line per header, in the form Header-Name: value, ending with a Carriage Return and a Line Feed (CRLF). There are forty-six headers defined by HTTP 1.1, but only one - the Host - is required in requests. Usually, the From and User-Agent fields are also sent, at a minimum. The headers that interest us are those that deal with caching.

Typically, a request header field will contain the name of the field and its directive(s), or instruction(s), separated by a colon. Each header has its own set of possible directives to choose from. Multiple directives can be provided by separating each by a comma (,). Some headers take name=value pair tokens instead of directives. Here is the format for the Pragma directive: