How to set up caching for css/js static properly
to prevent problem, when I update CSS/JS media files and browsers dont request new version, because they cache these files I used this solution: https://github.com/jaddison/django-cachebuster, that adds ?<timestamp of file> to CSS/JS filenames (replaces /media/main.css with /media/main.css?20012931203128. I assumed that it will force browsers to reload css file when timestamp is changed (file is updated) and use local cached version in other case. But what I see in Apache logs (and at firebug) is that browsers (at least Firefox) requests CSS/JS files for each reload of the page, even after getting 304 code, see fragment from logs:
XXX.255.115.60 - - [24/Jul/2011:04:17:25 -0700] "GET /media/main.css?333900240611 HTTP/1.1" 304 172 "" "Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
XXX.255.115.60 - - [24/Jul/2011:04:17:26 -0700] "GET /media/main.js?270101180511 HTTP/1.1" 304 173 "" "Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
XXX.255.115.60 - - [24/Jul/2011:04:17:34 -0700] "GET /media/main.css?333900240611 HTTP/1.1" 304 172 "" "Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
XXX.255.115.60 - - [24/Jul/2011:04:17:35 -0700] "GET /media/main.js?270101180511 HTTP/1.1" 304 173 "" "Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
XXX.255.115.60 - - [24/Jul/2011:04:17:44 -0700] "GET /media/main.css?333900240611 HTTP/1.1" 304 172 "" "Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
XXX.255.115.60 - - [24/Jul/2011:04:17:44 -0700] "GET /media/main.js?270101180511 HTTP/1.1" 304 173 "" "Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
Of course, it makes my website slower. Is it possible to force browsers to update files only when timestamp after .css?... is changed? Thanks!
Upd: Here is an example of response & request:
Request
User-Agent Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18
Accept text/css,*/*;q=0.1
Accept-Language en-gb,en;q=0.5
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive 115
Referer
DNT 1
Connection keep-alive
If-Modified-Since Fri, 24 Jun 2011 05:39:33 GMT
If-None-Match "8ed02f1-a21-4a66ea04f2f40"
Response
Date Sun, 24 Jul 2011 12:28:21 GMT
Server Apache
Connection Keep-Alive
Keep-Alive timeout=2, max=99
Etag "8ed02f1-a21-4a66ea04f2f40"
Initial resp
Date Sun, 24 Jul 2011 12:51:05 GMT
Server Apache
Last-Modified Fri, 24 Jun 2011 05:39:33 GMT
Etag "8ed02f1-a21-4a66ea04f2f40"
Accept-Ranges bytes
Content-Length 2593
Keep-Alive timeout=2, max开发者_JAVA百科=99
Connection Keep-Alive
Content-Type text/css
You should use proper HTTP caching directives to make the responses never expire (one year in future):
Cache-Control: max-age=31536000
Expires: Sun, 24 Jul 2012 12:51:05 GMT
You can do this in Apache with mod_expires:
ExpiresByType text/css "now plus 1 year"
ExpiresByType text/javascript "now plus 1 year"
I played with this a lot and I found that it is probably caused by an Apache bug. This is not problem of the browser - once the timesatmp is in a query string, the browser doesn't understand the query string and must at least ask the server if it hasn't changed. The Apache responds well with '304 Not modified', but it always sends the file too!
I made a lot of experiment and the only solution that worked was to place the timestamp directly to file name. In PHP, I use the following function:
function safe_inline_url($file)
{
$basename = basename($file);
if (strstr($basename, '.'))
return dirname($file) . "/" .
preg_replace('/(\.[^\.]*)$/', '_ts_' . filemtime($file) . '\1',
$basename);
else
return $file . '_ts_' . filemtime($file);
}
which modifies any inline URL (css, js, images, ...) so that it adds timestamp, eg. like js/forms.js => js/forms_ts_1278080148.js .
At the server, you must then rewrite the modified filenames back to the real names, which is accomplished by putting this to your .htaccess file:
RewriteEngine On
RewriteCond %{REQUEST_URI} ^(.*)_ts_[0-9]{9,}$
RewriteRule ^ %1 [PT]
RewriteCond %{REQUEST_URI} ^(.*)_ts_[0-9]{9,}\.(.*)$
RewriteRule ^ %1.%2 [PT]
you put it to root directory and you also must put it to every subdirectory's .htaccess with RewriteEngine On.
The only issue was with scriptaculous javascript library which uses URLs to include other sources - you cannot use this trick to include their js files.
精彩评论