浏览器缓存机制
当我们的前端工程部署到静态文件服务器后,服务器向浏览器分发静态资源是可以指定缓存机制的。正确配置缓存能够达到加快用户平均页面载入速度的效果,但错误的缓存配置则可能导致用户浏览器中的缓存不更新,甚至页面报错等一系列问题。
HTTP协议中的缓存控制
强缓存:浏览器第一次请求资源时,服务端指定了资源的过期时间,浏览器在过期时间之前取本地缓存,在过期之间之后重新从服务端获取资源。
协商缓存:浏览器第一次请求资源时,服务端返回最后修改时间戳或etag对资源进行标识,浏览器再次请求资源时会带上这些标识,由服务端判断是否应该使用缓存。如果使用缓存,服务端会返回304状态码。
强缓存
HTTP1.0协议中,强缓存使用Expires响应头控制,它返回缓存的到期时间。
HTTP1.1协议中,新增使用Cache-Control控制强缓存,该请求头有以下取值:
no-store:禁用缓存。no-cache:不使用强缓存,使用协商缓存。max-age=3600:缓存的过期时长,单位为秒。超过该时长后,浏览器会使用协商缓存。如果设置max-age=0,效果同no-cache。
注意:如果Cache-Control和Expires同时存在,前者的优先级更高。
协商缓存
协商缓存有Last-Modified和ETag两种方式。
Last-Modified的协商流程如下:
- 浏览器第一次访问资源时,服务端响应
Last-Modified响应头,其中包含了文件的最后修改时间 - 浏览器再次访问资源时,带上
If-Modified-Since请求头,内容和资源的原Last-Modified值相同。 - 服务端对该值和当前的
Last-Modified值进行对比,判断是否需要响应资源。如果判断资源已更新,返回200状态码和新资源;否则返回304状态码。
不过Last-Modified有一些缺点,服务端的文件系统可能更新了资源的Last-Modified值,但资源的实际内容并未更新,此时也会重复返回资源,因此更好的方式是使用ETag。
ETag是服务端生成的一个唯一标识符,默认情况下Nginx使用文件的Inode + FileSize作为资源的ETag。
ETag的协商流程如下:
- 浏览器第一次访问资源时,服务端响应
ETag响应头。 - 浏览器再次访问资源时,带上
If-None-Match请求头,内容和资源的原ETag值相同。 - 服务端对该值和当前的
ETag值进行对比,判断是否需要响应资源。如果判断资源已更新,返回200状态码和新资源;否则返回304状态码。
如果Last-Modified和ETag同时存在,后者的优先级更高。
Nginx禁用资源的缓存
通过上面内容我们可以发现,缓存机制的正确执行十分依赖客户端(浏览器或WebView)的实现。在实际开发中尤其是移动端,我们用React等框架开发的前端工程可能在某些客户端下出现资源被缓存导致未更新的问题,这可能是由于客户端实现的问题造成协商缓存规则不生效,此时比较简单的解决方案就是直接使用no-store禁用HTML的缓存,至于JS、CSS等资源一般在WebPack打包时加上了Hash,因此通常无需禁用缓存,只需要禁用HTML的缓存即可。下面例子Nginx配置中,我们为HTML文件单独指定了禁用缓存的策略。
server {
listen 8080;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
if ($request_filename ~* .*\.(htm|html)$) {
add_header Cache-Control "no-store";
}
}
}