浏览器系列之分析 304 状态码过程,图解 HTTP 强缓存和协商缓存
yuanyuanbyte opened this issue · 0 comments
本系列的主题是浏览器,每期讲解一个技术要点。如果你还不了解各系列内容,文末点击查看全部文章,点我跳转到文末。
如果觉得本系列不错,欢迎 Star,你的支持是我创作分享的最大动力。
分析 304 状态码过程,图解 HTTP 强缓存和协商缓存
什么是缓存
缓存:保存资源副本并在下次请求时直接使用该副本。
HTTP 缓存分为 2 种,一种是强缓存,另一种是协商缓存。
为什么需要缓存?
缓存的主要作用是可以加快资源获取速度,提升用户体验,减少网络传输,缓解服务端的压力。。
强缓存
强缓存就是不需要发送请求到服务端,直接读取浏览器本地缓存,在 Chrome 的 Network 中显示的 HTTP 状态码是 200 。
在 Chrome 中,强缓存又分为 Disk Cache (存放在硬盘中)和 Memory Cache (存放在内存中),存放的位置是由浏览器控制的。是否强缓存由 Expires、Cache-Control 和 Pragma 3 个 Header 属性共同来控制。
Expires
Expires
的值是一个 HTTP 日期,在浏览器发起请求时,会根据系统时间和 Expires
的值进行比较,如果系统时间超过了 Expires
的值,缓存失效。由于和系统时间进行比较,所以当系统时间和服务器时间不一致的时候,会有缓存有效期不准的问题。Expires
的优先级在三个 Header 属性中是最低的。
Cache-Control
Cache-Control
是 HTTP/1.1 中新增的属性,在请求头和响应头中都可以使用,常用的属性值如有:
- max-age:单位是秒,缓存时间计算的方式是距离发起的时间的秒数,超过间隔的秒数缓存失效
- no-cache:不使用强缓存,需要与服务器验证缓存是否新鲜
- no-store:禁止使用缓存(包括协商缓存),每次都向服务器请求最新的资源
- private:专用于个人的缓存,中间代理、CDN 等不能缓存此响应
- public:响应可以被中间代理、CDN 等缓存
- must-revalidate:在缓存过期前可以使用,过期后必须向服务器验证
Pragma
Pragma
只有一个属性值,就是 no-cache
,效果和 Cache-Control
中的 no-cache
一致,不使用强缓存,需要与服务器验证缓存是否新鲜,在 3 个头部属性中的优先级最高。
本地通过 express
起一个服务来验证强缓存的 3 个属性,代码如下:
const express = require('express');
const app = express();
var options = {
etag: false, // 禁用协商缓存
lastModified: false, // 禁用协商缓存
setHeaders: (res, path, stat) => {
res.set('Cache-Control', 'max-age=10'); // 强缓存超时时间为10秒
},
};
app.use(express.static((__dirname + '/public'), options));
app.listen(3000);
第一次加载,页面会向服务器请求数据,并在 Response Header
中添加 Cache-Control
,过期时间为 10 秒。
第二次加载,Date 头属性未更新,可以看到浏览器直接使用了强缓存,实际没有发送请求。
过了 10 秒的超时时间之后,再次请求资源:
当 Pragma
和 Cache-Control
同时存在的时候,Pragma
的优先级高于 Cache-Control
。
协商缓存
协商缓存是一种服务端的缓存策略,即通过服务端来判断某件请求是否可以命中缓存。
服务端判断客户端的资源,是否和服务端资源一样,如果一致则返回 304 ,反之返回 200 和最新的资源。
当浏览器的强缓存失效的时候或者请求头中设置了不走强缓存,并且在请求头中设置了If-Modified-Since
或者 If-None-Match
的时候,会将这两个属性值到服务端去验证是否命中协商缓存,如果命中了协商缓存,会返回 304 状态,加载浏览器缓存,并且响应头会设置 Last-Modified
或者 ETag
属性。
ETag/If-None-Match
ETag
/ If-None-Match
的值是一串 hash 码,代表的是一个资源的标识符,当服务端的文件变化的时候,它的 hash码会随之改变,通过请求头中的 If-None-Match
和当前文件的 hash 值进行比较,如果相等则表示命中协商缓存。ETag
又有强弱校验之分,如果 hash 码是以 "W/" 开头的一串字符串,说明此时协商缓存的校验是弱校验的,只有服务器上的文件差异(根据 ETag 计算方式来决定)达到能够触发 hash 值后缀变化的时候,才会真正地请求资源,否则返回 304 并加载浏览器缓存。
Last-Modified/If-Modified-Since
Last-Modified
/ If-Modified-Since
的值代表的是文件的最后修改时间,第一次请求服务端会把资源的最后修改时间放到 Last-Modified
响应头中,第二次发起请求的时候,请求头会带上上一次响应头中的 Last-Modified
的时间,并放到 If-Modified-Since
请求头属性中,服务端根据文件最后一次修改时间和 If-Modified-Since
的值进行比较,如果相等,返回 304 ,并加载浏览器缓存。
本地通过 express 起一个服务来验证协商缓存,代码如下:
const express = require('express');
const app = express();
var options = {
etag: true, // 开启协商缓存
lastModified: true, // 开启协商缓存
setHeaders: (res, path, stat) => {
res.set({
'Cache-Control': 'max-age=00', // 浏览器不走强缓存
'Pragma': 'no-cache', // 浏览器不走强缓存
});
},
};
app.use(express.static((__dirname + '/public'), options));
app.listen(3001);
第一次请求资源:
第二次请求资源,服务端根据请求头中的 If-Modified-Since
和 If-None-Match
验证文件是否修改。
ETag
/ If-None-Match
的出现主要解决了 Last-Modified
/ If-Modified-Since
所解决不了的问题
- 当响应头部 Response Headers 同时存在
Last-Modified
和Etag
的值时,会优先使用Etag
; Last-Modified
只能精确到秒级;- 如果资源被重复生成,而内容不变,则
Etag
更精确
即:
- 如果文件的修改频率在秒级以下,
Last-Modified
/If-Modified-Since
会错误地返回 304 - 如果文件被修改了,但是内容没有任何变化的时候,
Last-Modified
/If-Modified-Since
会错误地返回200 并返回资源
如果看完前面的文章后对
HTTP 强缓存和协商缓存
还不是特别理解,可以继续看下面的文章,或许能帮到你;
如果前面的文章已经帮你理解了 HTTP 强缓存和协商缓存,则可以略过下面的文章。
强缓存
先看第一个图:
从上图可以看到,当初次请求时,浏览器会向服务器发起请求,服务器接收到浏览器的请求后,返回资源并返回一个 Cache-Control
给客户端,该 Cache-Control
一般设置缓存的最大过期时间。
接下来看第二个图:
从上图中可以看到,此时浏览器已经接收到 cache-control
的值,那么这个时候浏览器再次发送请求时,它会先检查它的 cache-control
是否过期,如果没有过期则直接从本地缓存中拉取资源,返回到客户端,而无需再经过服务器。
接下来看第三个图:
强制缓存有过期时间,那么就意味着总有一天缓存会失效。那么假设某一天,客户端的 cache-control 失效了,那么它就没办法从本地缓存中拉取资源。于是它会像第一张图一样,重新向服务器发起请求,之后服务器会再次返回资源和 cache-control
的值。
以上就是强制缓存的全过程。
协商缓存
先来看第一张图:
在上图中,表明了协商缓存的全过程。首先,如果客户端是第一次向服务器发出请求,则服务器返回资源和相对应的资源标识给浏览器。该资源标识就是对当前所返回资源的一种唯一标识,可以是Etag
或者是Last-Modified
,这两个字段将在图例结束后展开讲解。
之后如果浏览器再次发送请求时,浏览器就会带上这个资源标识。此时,服务端就会通过这个资源标识,可以判断出浏览器的资源跟服务端此时的资源是否一致,如果一致,则返回304,即表示Not Found 资源未修改。如果判断结果为不一致,则返回200,并返回资源以及新的资源标识。至此就结束了协商缓存的过程。
接下来看第二张图:
假设此时我们的协商缓存用 Last-Modified
来判断。当浏览器第一次发送请求时,服务器返回资源并返回一个 Last-Modified
的值给浏览器。这个 Last-Modified
的值给到浏览器之后,浏览器会通过 If-Modified-Since
的字段来保存 Last-Modified
的值,且 If-Modified-Since
保存在请求头当中。
之后当浏览器再次发送请求时,请求头会带着 If-Modified-Since
的值去找服务器,服务器此刻就会匹配浏览器发过来的 If-Modified-Since
是否和自己最后一次修改的 Last-Modified
的值相等。如果相等,则返回 304 ,表示资源未被修改;如果不相等,则返回200,并返回资源和新的 Last-Modified 的值。
接下来看第三张图:
假设此时我们的协商缓存用 Etag
来判断。当浏览器第一次发送请求时,服务器返回资源并返回一个 Etag
的值给浏览器。这个 Etag
的值给到浏览器之后,浏览器会通过 If-None-Match
的字段来保存 Etag
的值,且 If-None-Match
保存在请求头当中。
之后当浏览器再次发送请求时,请求头会带着 If-None-Match
的值去找服务器,服务器此刻就会匹配浏览器发过来的 If-None-Match
是否和自己最后一次修改的 Etag
的值相等。如果相等,则返回 304 ,表示资源未被修改;如果不相等,则返回 200 ,并返回资源和新的 Etag
的值。
参考
博文系列目录
- JavaScript 深入系列
- JavaScript 专题系列
- JavaScript 基础系列
- 网络系列
- 浏览器系列
- Webpack 系列
- Vue 系列
- 性能优化与网络安全系列
- HTML 应知应会系列
- CSS 应知应会系列
交流
各系列文章汇总:https://github.com/yuanyuanbyte/Blog
我是圆圆,一名深耕于前端开发的攻城狮。