原文链接:
https://segmentfault.com/a/1190000015816331
1.HTTP缓存
浏览器第一次向一个web服务器发起http请求后,服务器会返回请求的资源,并且在响应头中添加一些有关缓存的字段如:Cache-Control、Expires、Last-Modified、ETag、Date等等。之后浏览器再向该服务器请求该资源就可以视情况使用强缓存和协商缓存。
- 强缓存:浏览器直接从本地缓存中获取数据,不与服务器进行交互。
- 协商缓存:浏览器发送请求到服务器,服务器判定是否可使用本地缓存。
联系与区别:
- 两种缓存方式最终使用的都是本地缓存;
- 前者无需与服务器交互,后者需要。
2. 强缓存
如上图红线所示的过程代表强缓存。用户发起了一个http请求后,浏览器发现先本地已有所请求资源的缓存,便开始检查缓存是否过期。有两个http头部字段控制缓存的有效期:Expires和Cache-Control,浏览器是根据以下两步来判定缓存是否过期的:
- 查看缓存是否有Cache-Control的s-maxage或max-age指令,若有,则使用响应报文生成时间Date +
s-maxage/max-age获得过期时间,再与当前时间进行对比(s-maxage适用于多用户使用的公共缓存服务器); - 如果没有Cache-Control的s-maxage或max-age指令,则比较Expires中的过期时间与当前时间。Expires是一个绝对时间。
注意:
在HTTP/1.1中,当首部字段Cache-Control有指定s-maxage或max-age指令,比起首部字段Expires,会优先处理s-maxage或max-age。
另外下面列几个Cache-Control的常用指令:
- no-cache:含义是不使用本地缓存,需要使用协商缓存,也就是先与服务器确认缓存是否可用。
- no-store:禁用缓存。
- public:表明其他用户也可使用缓存,适用于公共缓存服务器的情况。
- private:表明只有特定用户才能使用缓存,适用于公共缓存服务器的情况。
经过上述两步判断后,若缓存未过期,返回状态码为200,则直接从本地读取缓存,这就完成了整个强缓存过程;如果缓存过期,则进入协商缓存或服务器返回新资源过程。
3. 协商缓存
当浏览器发现缓存过期后,缓存并不一定不能使用了,因为服务器端的资源可能仍然没有改变,所以需要与服务器协商,让服务器判断本地缓存是否还能使用。
此时浏览器会判断缓存中是否有ETag或Last-Modified字段,如果没有,则发起一个http请求,服务器根据请求返回资源;如果有这两个字段,则在请求头中添加If-None-Match字段(有ETag字段的话添加)、If-Modified-Since字段(有Last-Modified字段的话添加)。
注意:如果同时发送If-None-Match 、If-Modified-Since字段,服务器只要比较If-None-Match和ETag的内容是否一致即可;如果内容一致,服务器认为缓存仍然可用,则返回状态码304,浏览器直接读取本地缓存,这就完成了协商缓存的过程,也就是图中的蓝线;如果内容不一致,则视情况返回其他状态码,并返回所请求资源。
下面详细解释下这个过程:
1.ETag和If-None-Match
二者的值都是服务器为每份资源分配的唯一标识字符串。
- 浏览器请求资源,服务器会在响应报文头中加入ETag字段。资源更新时,服务器端的ETag值也随之更新;
- 浏览器再次请求资源时,会在请求报文头中添加If-None-Match字段,它的值就是上次响应报文中的ETag的值;
- 服务器会比对ETag与If-None-Match的值是否一致,如果不一致,服务器则接受请求,返回更新后的资源;如果一致,表明资源未更新,则返回状态码为304的响应,可继续使用本地缓存,要注意的是,此时响应头会加上ETag字段,即使它没有变化。
2.Last-Modified和If-Modified-Since
二者的值都是GMT格式的时间字符串。
- 浏览器第一次向服务器请求资源后,服务器会在响应头中加上Last-Modified字段,表明该资源最后一次的修改时间;
- 浏览器再次请求该资源时,会在请求报文头中添加If-Modified-Since字段,它的值就是上次服务器响应报文中的Last-Modified的值;
- 服务器会比对Last-Modified与If-Modified-Since的值是否一致,如果不一致,服务器则接受请求,返回更新后的资源;如果一致,表明资源未更新,则返回状态码为304的响应,可继续使用本地缓存,与ETag不同的是:此时响应头中不会再添加Last-Modified字段。
3.ETag较之Last-Modified的优势
你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要ETag呢?
HTTP1.1中ETag的出现主要是为了解决几个Last-Modified比较难解决的问题:
-
一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;
-
某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);
-
某些服务器不能精确的得到文件的最后修改时间。
这时,利用ETag能够更加准确的控制缓存,因为ETag是服务器自动生成的资源在服务器端的唯一标识符,资源每次变动,都会生成新的ETag值。Last-Modified与ETag是可以一起使用的,但服务器会优先验证ETag。
4. 用户行为的影响
一张图说明用户行为对浏览器缓存的影响