前言
http缓存是WEB开发中比较重要的知识,缓存的作用是减少没必要的网络请求,提升页面加载速度,优化用户体验,减轻了服务器的压力,节省了带宽。我们必须掌握缓存的基本知识,才能利用好缓存,也能帮助我们解决开发中因客户端缓存导致资源不更新问题。
http缓存分为强制缓存和协议缓存
强制缓存:
在资源文件没有过期前不会发送请求到服务器,直接使用本地缓存(磁盘、内存),强缓存通过Expires、Cache-Control 和 Pragma控制
1. Expires
它是http1.0定义的字段,属性值是一个时间戳,当客户端再次请求该资源的时候,会把客户端时间与该时间戳进行对比,如果大于该时间戳则已过期,否则直接使用该缓存资源。
Expires的一个缺点就是,返回的到期时间是服务器端的时间,这样存在一个问题,如果客户端的时间与服务器的时间相差很大,那么误差就很大,所以在HTTP 1.1版开始,使用Cache-Control: max-age=秒替代,如果Cache-Control与expires同时存在,Cache-Control生效。
2. Cache-Control
这是http1.1新增的字段,常用的属性值如有:
- max-age:单位是秒,缓存时间计算的方式是距离发起的时间的秒数,超过间隔的秒数缓存失效
- no-cache:不使用强缓存,需要与服务器验证缓存是否新鲜
- no-store:禁止使用缓存(包括协商缓存),每次都向服务器请求最新的资源
- private:专用于个人的缓存,中间代理、CDN 等不能缓存此响应
- public:响应可以被中间代理、CDN 等缓存
- must-revalidate:在缓存过期前可以使用,过期后必须向服务器验证
3. Pragma
也是http1.0定义的字段,只有一个属性值:no-cache,作用和Cache-Control中的no-cache一样,优先级高于 cache-control 和 expires;三者同时出现时,优先级 pragma -> cache-control -> expires
协商缓存:
当强缓存失效或者强缓存设置no-cache等不使用强缓存情况下,如果前一次请求的response header中有Last-Modified,ETag,浏览器就会在request header中携带If-Modified-Since(上一次请求返回的Last-Modified值),If-None-Match(上一次请求返回的ETag值),服务器根据这两个参数和服务资源对比看是否过期,如果没有过期服务器返回304,浏览器使用本地缓存,否则返回200,返回服务最新的资源文件。
1. Last-Modified / If-Modified-Since
- 当浏览器第一次请求一个url时,服务器端的返回状态码为200,同时HTTP响应头会有一个Last-Modified标记着文件在服务器端最后被修改的时间。
- 浏览器第二次请求上次请求过的url时,浏览器会在HTTP请求头添加一个If-Modified-Since的标记,用来询问服务器该时间之后文件是否被修改过。
- 如果服务器端的资源没有变化,则自动返回304状态,使用浏览器缓存,从而保证了浏览器不会重复从服务器端获取资源,也保证了服务器有变化是,客户端能够及时得到最新的资源。
2. ETag / If-None-Match
- 当浏览器第一次请求一个url时,服务器端的返回状态码为200,同时HTTP响应头会有一个ETag,存放着服务器端生成的一个序列值。
- 浏览器第二次请求上次请求过的url时,浏览器会在HTTP请求头添加一个If-None-Match的标记,用来询问服务器该文件有没有被修改。
有Last-Modified为什么还要ETag? ETag 主要为了解决 Last-Modified 无法解决的一些问题:
- 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET。
- 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒)
- 某些服务器不能精确的得到文件的最后修改时间
我们通过图示看下浏览器缓存流程:
1. 浏览器第一次请求
2. 浏览器第二次请求
问题:
-
问题一
A:如果response header中没有Cache-Control等强缓存字段呢,浏览器怎么处理?
Q:如果没有强缓存字段浏览器会用 (当前时间-最后修改时间)*10% 计算出来的时间当成max-age,这个缓存时间不一定的,经常开发没问题但测试或上生产会出问题,当然这个计算公式不是所有浏览器都是这样的,因为没有明确指定缓存策略,浏览器厂商就按自己认为合适的策略进行缓存。 -
问题二
A:如何解决入口页面缓存,比如微信缓存?
Q:入口页面应该加上Cache-Control:no-cache或者设置max-age一个比较短的时间,需要在http响应头中设置,不是html页面的meta标签,meta标签有的浏览器认有的浏览器不认。 -
问题三
A:如何解决IE8,IE9 ajax get请求缓存问题?
Q:后台接口统一返回头中统一加上Cache-Control:no-cache,百度网盘,网易邮箱等后台接口返回的header都是no-cache或max-age=0的,当然前端框架加随机数等方式也能解决,比如jquery ajax的cache:false。
总结&实践
- 对于前端,入口页面使用no-cache或max-age设置为较短时间,js,css本身设置max-age为较长时间,引用js,css通过打包工具加hash版本号方式,这样浏览器只会请求修改的文件,没有修改的直接用缓存。
- 对于后端接口,最好都加上no-cache,这样在老旧的浏览器上不会出问题。
- 明确指定强制缓存字段和协商缓存字段,对不同资源制定合适的缓存策略,而不是依赖浏览器的默认规则有助于解决很多缓存兼容问题。