views:

32

answers:

3

The Problem

There's an item (foo.js) that rarely changes. I'd like this item to be stored in the browser's cache (using Expires header). However, when it does change, I'd like the browser to update to the newest version.

The Attempt

Foo.js is returned with a far future Expires header. It's cached on the browser and requires no round trip query to the server. Just the way I like it. Now, when it changes....

Let's assume I know that the user's version of foo.js is outdated. How can I force a fresh copy of it to be obtained? I use xhr to perform a POST to foo.js. This should, in theory, force the browser to get a newer version of foo.js.

Unfortunately, this only seems to work in Firefox. Other browsers will use their cached version of the copy, even if other POST paramters are set.

WTF

First off, is there a way to do what I'm trying to do?

Second, why is there no sensible key/value type of cache that browser's have? Why can I not simply not include in headers: "Cache: some_key, some_expiration_time" and also specify "Clear-Cache: key1, key2, key3" (the keys must be domain specific, of course). Instead, we're stuck with either expensive round-trips that ask "is content new?", or the ridiculous "guess how long it'll be before you modify something" Expires header.

Thanks

Any comments on this matter are greatly appreciated.

Edits

  • I realize that adding a version number to the file would solve this. However, in my case it is not possible -- the call to "foo.js" is hardcoded into a bookmarklet.
A: 

Why set your cache expiration so far in the future? If you set it to one day for instance, the only overhead that you will incur (once a day) is the browser revalidating that it is the same file. If you still have not changed it, then you will not re-download the file, the server will respond with a not-modified response.

All caches have a set of rules that they use to determine when to serve a representation from the cache, if it’s available. Some of these rules are set in the protocols (HTTP 1.0 and 1.1), and some are set by the administrator of the cache (either the user of the browser cache, or the proxy administrator).

Generally speaking, these are the most common rules that are followed (don’t worry if you don’t understand the details, it will be explained below):

  1. If the response’s headers tell the cache not to keep it, it won’t.
  2. If the request is authenticated or secure (i.e., HTTPS), it won’t be cached.
  3. A cached representation is considered fresh (that is, able to be sent to a client without checking with the origin server) if: * It has an expiry time or other age-controlling header set, and is still within the fresh period, or * If the cache has seen the representation recently, and it was modified relatively long ago. Fresh representations are served directly from the cache, without checking with the origin server.
  4. If an representation is stale, the origin server will be asked to validate it, or tell the cache whether the copy that it has is still good.
  5. Under certain circumstances — for example, when it’s disconnected from a network — a cache can serve stale responses without checking with the origin server.

If no validator (an ETag or Last-Modified header) is present on a response, and it doesn't have any explicit freshness information, it will usually — but not always — be considered uncacheable.

Together, freshness and validation are the most important ways that a cache works with content. A fresh representation will be available instantly from the cache, while a validated representation will avoid sending the entire representation over again if it hasn’t changed.

http://www.mnot.net/cache_docs/#BROWSER

Tommy
I am trying to eliminate any roundtrips that need to occur unless they are necessary -- that means using Expires. The reason I'm using far-future is because I'm serving a script whose loading time is extremely important. If it's slow once a day when it should be able to be cached indefinitely, that's a loss on my part. To me, it's absolutely pathetic that there's no domain specific key/value protocol to cache items.
Sambo
@sambo: after reading the above link, I don't think it would be slow once a day unless you were updating it once a day. Perhaps I am mistaken, but the overhead of a file check should be relatively quick (200 bytes maybe)? But, it was just my 2 cents :) Hopefully, you can find a good solution to your issue.
Tommy
@Tommy: Well, it's sadly a tad more complicated. Instead of foo.js, its actually foo.js?user_id=XYZ. So, I'll have to manually check their If-Modified-Since request header against the last time I changed the code and respond accordingly.The #1 important thing is having foo.js load as instantly as possible. You're right though, a quick roundtrip exchange of headers is probably fast enough, and I'll likely settle for that. Thanks for your 2 cents :)
Sambo
+2  A: 

You can just add a querystring to the end of the file, the server can ignore it, but the browser can't, it must treat it as a new request:

http://www.site.com/foo.js?v=1.12345

Many people use this approach, SO uses a hash of some sort, I use the build number (so users get a new version each build). If either of these is an option, you get the benefit of long duration cache headers, but still force a fetch of a new copy when needed.

Nick Craver
Apologies that I neglected to mention that in the question: The request to "foo.js" cannot be altered. The reason is that it is being store in a bookmarklet.
Sambo
@Sambo - The bookmarklet can't do `"url/foo.js?" + (new Date().getTime())`, or something to that effect? That is, if you always want to fetch it, or alter that slightly to be daily, etc.
Nick Craver
It is vital that the bookmarklet load as fast as possible. When cached, it's essentially instant. Also, foo.js rarely changes. It's just that when it does, I want the browser to get a new copy of it. I'll probably just set the Expire for a week, and deal with users sometimes using an old version.
Sambo
@Sambo - How would you know when it changes? You could also set the expires header to a specific date, and plan your releases weekly for example...just set the cache header to expire just after your releases would be out there, so they fetch a very fresh version, is that an option?
Nick Craver
@Nick: The way I can tell if it has changed is: foo.js creates an iframe, and passes along its version number to the iframe src (for example: version_check.php?version=<foo.js version>). The server determines if there's a new version, and if so, returns a script/headers/whatever to refresh the cache. As of now, it does an xhr to foo.js to refresh the cached version. This works brilliantly in Firefox, but all other browsers simply have xhr load the cached version, even if I use POST and even if I pass random values in the POST parameters.
Sambo
A: 

There is an excellent suggestion made in this thread: http://stackoverflow.com/questions/3224/how-can-i-make-the-browser-see-css-and-javascript-changes

See the accepted answer by user, "grom".

The idea is to use the "modified" time stamp from the server to note when the file has been modified, and adding a version parameter to the end of the URL, making your CSS and JS files have URLs like this: my.js?version=12345678

This makes the browser think it is a new file, and so it does not refer to the cached version.

I am using a similar method in my app. It works pretty well. Of course, this would assume you are using something like PHP to process your HTML.

Here is another link with a more simple implementation for WordPress: http://markjaquith.wordpress.com/2009/05/04/force-css-changes-to-go-live-immediately/

Jeff Fohl
I'm aware of this method, but forgot to mention that it's not a solution in my case. I've edited the question to reflect that. My apologies.
Sambo