当浏览器请求一个之前访问过的静态文件时,会在 HTTP 头中加上:If-Modified-Since、If-None-Match以向服务器确认该文件是否有更新,如果没有更新,服务器则返回 304 Not Modified,浏览器就会使用本地缓存而不是从服务器重新下载该文件。这是多数 Web Server 对于静态文件的默认缓存行为,然而这个缓存行为仍有优化空间。默认的缓存行为依然需要浏览器与服务器建立一个 HTTP 连接以确定每个文件是否更新过,从客户端建立连接、发送连接、服务器判断文件状态到最终客户端收到 304 Not Modified 之间仍需要几十甚至上百毫秒并且消耗一定的服务器资源。
第一次访问网站:
第二次访问网站:
从上面两个图可以看到,虽然第二次访问静态文件全都使用了本地缓存,但是全部资源加载任然花了将近 1 秒,每个 HTTP 304 连接都需要 150 毫秒左右。
其实这些步骤可以全部省略,我们可以强制浏览器直接使用本地的缓存,这样就不存在任何网络的传输和服务器消耗了。
Cache-Control & Expires
Cache-Control 可以通过设置 max-age 值来指定缓存的有效期(单位:秒)。
Cache-Control: max-age=3153600
Expires 可以指定一个特定的缓存过期时间,效果同 Cache-Control 的 max-age 一样。
Expires: Fri, 01 Jul 2016 00:00:00 GMT
使用了上述任一方法后,浏览器在缓存过期前将一直使用本地缓存而不再与服务器确认此文件是否有更新,整体加载时间显著降低。
如何更新文件?
这种极端缓存策略的弊端显而易见:浏览器将一直使用本地缓存而导致得不到更新。解决方法其实很简单,浏览器是根据文件的 URL 来判断是否使用缓存的,那么当文件更新后,只要确保 html 内文件 URL 发生变化,浏览器就会从服务器重新下载这个文件,当然我们一定要确保 html 文件本身没有设置那么长的缓存时间。文件 URL 大致可以有下面几种变化模式:
- 项目自动打包时生成随机的文件名
<script type="text/javascript" src="../src/js/febfcddf6bc65078231ce9026c2b96ad.js"></script>
- 文件名中加入版本号
<script type="text/javascript" src="../src/js/lib/jquery-1.11.3.min.js"></script>
- URL 中加入版本参数
<script type="text/javascript" src="../src/js/default.js?v=2"></script>
如何添加 Cache-Control?
IIS
- 选择你要添加 Cache-Control 的文件目录,双击 HTTP Response Headers 。
- 单击 Set Common Headers...,勾选 Expire Web content 并设置你想要的 max-age 值。
- 你也可以直接编辑文件目录下的 web.config 来设置 Cache-Control。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="365.00:00:00" />
</staticContent>
</system.webServer>
</configuration>
Apache
- 编辑.htaccess 文件:
# 1 year for all your static assets
<filesMatch ".(ico|pdf|flv|jpg|jpeg|png|gif|js|css|swf)$">
Header set Cache-Control "max-age=31536000, public"
</filesMatch>
NodeJS
- 对于使用 Express Static 中间件,max-age 设置的单位是毫秒。
var oneYear = 60 * 1000 * 60 * 24 * 365;
app.use(express.static('staticFile', { maxAge: oneYear }));
- 原生 NodeJS 可以使用
setHeader
方法来添加 Cache-Control 。
response.setHeader('Cache-Control', 'max-age=31536000');