之前对于浏览器的缓存一直是一知半解,可能这块前端涉入较少,主要是由后端在控制。这两天看Nodejs,于是重点研究了下,在此做个总结。
缓存必要性:
浏览器打开一个网页,通常需要加载HTML、Javascript、CSS 等够成网页界面和逻辑的文件,加载这些东西是非常耗时的。而且这些内容在大部分情况下不会经常变更。因此浏览器提供了一种本地缓存机制,用户再次进入这个页面时,不需要再去向服务器端请求这些文件,而是直接从本地读取。这种机制减少了带宽浪费,同时也大大提高了用户体验。作为前端人员,研究下它的工作原理,还是很有必要的。
缓存实现方案:
方案一: If-Modified-Since 和 Last-Modified
浏览器第一次打开某个网址,必然是会请求服务器端的内容,并将返回的内容缓存在本地。文件返回头部都会带有一个 Last-Modified 字段,意思是最近一次的修改时间。如下所示:
Last-Modified:Wed, 18 Dec 2013 00:32:38 GMT
当浏览器再次访问该网址时,它会对本地文件进行检查,如果不能确定这份文件是否可以直接使用(如Expires过期), 将会发送一份请求给服务端,并且在请求头上带上If-Modified-Since字段,字段值为该文件的Last-Modified值:
If-Modified-Since:Wed, 18 Dec 2013 00:32:38 GMT
请求发送到服务器后,服务器端根据 If-Modified-Since 值是和文件的Last-Modified 值判断文件是否有更新。如果没有更新,返回状态码304,不需要返回文件内容,浏览器直接使用本地文件。否则,则返回200,并将新的内容返回给浏览器。
fs.stat(filename, function (err, stat) {
var lastModified = stat.mtime.toUTCString();
if(lastModified === req.headers[‘if-modified-since‘]) {
res.writeHead(304, ‘Not Modified‘);
res.end()
} else {
fs.readFile(filename, function (err, file) {
var lastModified = stat.mtime.toUTCString();
res.setHeader(‘Last-Modified‘, lastModified);
res.writeHead(200, ‘OK‘);
res.end();
});
}
});<nodejs 实现>
方案二: If-None-Match 和 ETag
方案一采用的是时间戳的方式实现,但是时间戳会有一些缺陷存在:
文件的时间戳改动但内容不一定改动
时间戳只能精确到秒级别,频繁的内容更新将无法生效
因此在HTTP 1.1 中引入了ETag。ETag 是由服务器端根据文件内容生成的一串散列值,散列值随着文件内容改变而改变,且不会存在上述的问题。判断原理和方案一差不多,只不过将判断文件的修改时间改成判断文件散列值是否相同。
fs.readFile(filename, function (err, file) {
var hash = getHash(file),
nonMatch = req[‘if-none-match‘];
if(hash === nonMatch) {
res.writeHead(304, ‘Not Modified‘);
res.end();
} else {
res.setHeader(‘ETag‘, hash);
res.writeHead(200, ‘OK‘);
res.end();
}
});<nodejs 实现>
方案三:设置 Expires
方案一和方案二尽管在文件内容没有改动时可以节省带宽,但依然会消耗一个HTTP请求。想要浏览器在加载文件时不发送请求,而是直接去读本地文件,可以在请求返回头设置expires属性。
expires 是一个GTM格式的时间字符串。服务器可以通过它设置文件在用户本地存储的过期时间。只要本地还存在这个缓存文件,在到期时间之前都不会再发送请求。如将Expires 设置为一个月以后:
Expires:Sat, 15 Feb 2014 11:12:03 GMT
在地址框中重新输入地址(注意不能按F5 或 Ctrl+R 刷新),查看chrome 控制台显示的请求信息如下:
状态信息显示的是200 OK (from cache)。在firebug 中这个请求直接会不可见。
方案四:设置Cache-Control
Cache-Control 功能更强大,它有一个 max-age 值,意思是文档被访问后的存活时间,单位为秒。这个时间是个相对值,相对的是文档第一次被请求时服务器记录的请求时间。max-age 能够避免Expires 带来的浏览器和服务前端时间不同步的问题。不过 HTTP 1.0不支持max-age,而且如果浏览器中max-age 和Expires 同时存在,且都被支持,max-age 会覆盖Expires。Cache-Control 由后台设置,在请求的返回头信息中出现。如下设置文档在客户端存活时间为2592000秒。
Cache-Control:max-age=2592000
强制不读本地缓存:
有些时候我们不希望浏览器去读去缓存数据,而是强制它去读去服务器端最新的数据。这种情况在ajax请求动态json数据的时候经常出现。解决方案如下:
1)在URL search 部分加入随机数,如 http://example.com/data?123456
2)设置请求头部的If-Modified-Since 属性为‘0‘。
清除缓存:
当版本号更新的时候,我们希望浏览器读取我们最新版本的文件,而不是旧版本的文件。这种情况,比较普遍的解决办法是在URL中加入版本号,让浏览器发起新的URL请求,避免读去到本地缓存或是CDN缓存内容。如下面URL, 版本号为1.0.1:
http://static.lanhuedu.com/kutianshi/1.0.1/assets/css/common.css
参考资料:《深入浅出Nodejs》