Testing ye olde “querystring cache busting” trick.
When setting your site’s file cache settings, there are 2 common ways to properly save bandwidth with HTTP headers.
First method: compare the browser cache’s current versions
Set an http header such as ETag or Last-Modified that allows a client (browser) to compare its cached version of a file with the one on the server. With ETag, for example, the client checks the ETag of its cached file against the server’s version of the file, and a 304 Not Modified is sent back to the client without the actual file contents.
This method saves bandwidth, but it also requires a GET or HEAD request to the server to find out if the browser needs a new version of the file.
Second method: give the file a lifetime in the cache
To avoid the lightweight GET/HEAD request, we can also set the Expires or Cache-Control: max-age headers. Expires will tell the browser the date that the file expires (so it can make a new request to the server), and max-age will tell the browser how long the file is good for (compared to the Date header returned in the response).
Cache Busting
A frequent use of the Expires/max-age method is to set a long life (eg 1 year) on all static files (images, css, js, etc.), and then change the url of the file on the page whenever you need to use a newer version of a file (ie “styles_v1.css” to “styles_v2.css”)
Using querystrings to sidestep the cached file (à la Rails) is a lightweight way (ie “styles.css?MODIFIED_TIME”), but has come under a little speculation (namely that the RFC doesn’t allow cacheing files with querystrings & that some browsers won’t allow it).
Test
So I took a basic html file and served it over Apache with the following headers on the images, javascript, and stylesheets:
HTTP/1.1 200 OK
Date: Wed, 18 Nov 2009 20:50:52 GMT
Server: Apache/2.2.11 (Unix) mod_ssl/2.2.11 OpenSSL/0.9.8k DAV/2 PHP/5.3.0 Phusion_Passenger/2.2.5
Accept-Ranges: bytes
Content-Length: XXXX
Expires: Wed, 18 Nov 2019 20:50:52 GMT
Content-Type: XXXX/XXXX
(notice that the only caching mechanism is setting the Expires header to 10 years in the future)
Browsers Tested
Here are the results of testing querystring cache busting on the html file:
- Opera 10.01: works (ETag/Last-Modified can be set too)
- FF3.5: works (ETag/Last-Modified can be set too)
- FF3.0: works (ETag/Last-Modified can be set too)
- FF2.0: works (ETag/Last-Modified can be set too)
- IE8: works (ETag/Last-Modified can be set too)
- IE7: ???
- IE6: works (ETag/Last-Modified can be set too)
- WebKit r51075: probably works (but ETag/Last-Modified seem to override Expires, so if they are set the Expires is ignored and the freshness check is requested)
- Safari4: probably works (same as Webkit, the ETag/Last-Modified seem to override Expires so they can’t be set)
What the RFC says
HTTP 1.1 SPEC
13.9 Side Effects of GET and HEAD
Unless the origin server explicitly prohibits the caching of their responses, the application of GET and HEAD methods to any resources SHOULD NOT have side effects that would lead to erroneous behavior if these responses are taken from a cache. They MAY still have side effects, but a cache is not required to consider such side effects in its caching decisions. Caches are always expected to observe an origin server’s explicit restrictions on caching.
We note one exception to this rule: since some applications have traditionally used GETs and HEADs with query URLs (those containing a “?” in the rel_path part) to perform operations with significant side effects, caches MUST NOT treat responses to such URIs as fresh unless the server provides an explicit expiration time. This specifically means that responses from HTTP/1.0 servers for such URIs SHOULD NOT be taken from a cache. See section 9.1.1 for related information.
This seems a little vague to me, but I interpret as: don’t cache files with querystrings when accepting HTTP/1.0 responses. The good news is that most of use 1.1 these days!
The Conclusion
So querystring cachebusting seems to be pretty well supported under the following conditions:
- you’re not serving HTTP/1.0
- you don’t include any cache headers that would require the client to check w/the server for freshness such as
Last-ModifiedorETag(you can do this as a fallback, but theExpires/max-agewill be useless for Safari/WebKit)
-
airve liked this
-
solid1pxred posted this