强缓存和协商缓存
浏览器的缓存机制是指浏览器可以缓存数据下来,当再次发起请求时,可以在浏览器的缓存中取出,无需再从服务器请求重复加载新数据。
浏览器的缓存机制是由强缓存和协商缓存实现的,其优点是减少网络数据传输,减少服务器负担,提升客服端加载速度。
强缓存和协商缓存的区别
- 如果命中强缓存,返回状态码
Status Code: 200 OK (from memory cache)
不会向服务器发起请求,直接从缓存中取出资源 - 如果命中协商缓存,会向服务器发起请求询问资源是否过期,如果未过期返回
Status Code: 304 Not Modified
无需要重新加载新数据,反之重新加载新数据。
图示
强缓存的机制
强缓存根据 expires 和 cache-control 字段来控制的。
首先在 HTTP1.1 版本前,是用 expires 字段控制强缓存,返回的是一个资源过期的时间戳。当想发起服务器请求时,浏览器会根据 expires 的时间戳和本地时间戳对比
,如果未过期,则使用浏览器的缓存。
缺点:[根据 expires 的时间戳和本地时间戳对比],如果修改本地时间,那就不准确了。所以HTTP1.1 版后引入了 cache-control: max-age
字段控制。
max-age
字段返回的是一个时间秒数,它会根据 response headers 返回的响应时间 + max-age
时间内,缓存都有效。
当然 cache-control 还有几个比较常用的值:
- public 可以被浏览器和代理服务器缓存
- private 只能被浏览器缓存(默认)
- s-maxage 和 max-age 同理,s-maxage 是代理服务器的有效期,s-maxage和max-age同时存在的换,s-maxage 优先
- no-store 当设置了这个值,浏览器会忽视所有缓存,每次都会向服务器发起新的请求,取最新的数据
- no-cache 当设置了这个值,就是协商缓存的机制,浏览器都会先发起向服务器询问请求,询问判断缓存的资源是否可用,如果服务器判断资源可用则返回 304 状态码,缓存数据可以继续使用,从浏览器取出数据即可;反之返回200状态,且最新数据。
Koa2 示例代码 - 强缓存
// 处理加载图片资源
router.get(/\S*\.(jpe?g|png)$/, async (ctx, next) => {
const { path } = ctx;
ctx.type = mime.getType(path)
const imagePath = Path.resolve(__dirname, `.${path}`)
const imageBuffer = await fs.readFile(imagePath)
// 同时存在,max-age 优先
// max-age 设置 120s 内有效
ctx.set('cache-control', 'max-age=120')
// expires 设置 60s 内有效
ctx.set('expires', new Date(Date.now() + 1 * 60 * 1000).toUTCString())
ctx.body = imageBuffer
await next()
})
协商缓存的机制
协商缓存的机制实现是根据2对字段实现的:
- Last-Modifiled / If-Last-Modifiled 根据文件的修改时间判断资源是否过期
- Etag / If-Node-Match 根据文件的修改内容生成唯一Key判断资源是否过期,与 Last-Modifiled 同时存在时,Etag 优先
Last-Modifiled / If-Last-Modifiled
首先、当第一次发起服务器请求数据响应成功回来时,会 Response Headers 会携带 Last-Modifiled 字段回来,该值是服务器返回该资源的修改时间。
然后、当再次发起服务器请求时,浏览器会把 Last-Modifiled 的值会设置到 Request Headers 中的 If-Last-Modifiled 值中,携带发送到服务器
最后服务器会根据 Request Headers 中的 If-Last-Modifiled 的值和 Last-Modifiled 的值对比是否相同,如果相同返回 Status Code: 304 Not Modified
,无需要重新加载新数据,反之重新加载新数据。
ETag / If-Node-Match
首先、当第一次发起服务器请求数据响应成功回来时,会 Response Headers 会携带 Etag 字段回来,该值是服务器返回的是根据该资源的最新修改内容生成唯一的Key。
然后、当再次发起服务器请求时,浏览器会把 Etag 的值会设置到 Request Headers 中的 If-None-Match 值中,携带发送到服务器
最后服务器会根据 Request Headers 中的 If-None-Match 的值和 Etag 的值对比是否相同,如果相同返回 Status Code: 304 Not Modified
,无需要重新加载新数据,反之重新加载新数据。
Koa2 示例代码 - 协商缓存
// 处理加载图片资源
router.get(/\S*\.(jpe?g|png)$/, async (ctx, next) => {
const { path } = ctx
ctx.type = mime.getType(path)
const imagePath = Path.resolve(__dirname, `.${path}`)
const imageStatus = await fs.stat(imagePath)
const lastModified = imageStatus.mtime.toGMTString()
const ifModifiedSince = ctx.request.headers['if-modified-since']
// 如果命中,返回状态304
if(ifModifiedSince === lastModified) {
ctx.status = 304
} else {
const imageBuffer = await fs.readFile(imagePath)
ctx.set('cache-control', 'no-cache')
ctx.set('last-modified', lastModified)
ctx.body = imageBuffer
}
await next()
})
详细代码仓库:https://github.com/lfb/http-cache,可以下载项目下来改改跑一下,体验一下印象会更深刻!