HTTP缓存
当浏览器加载一个页面时,html引用的外部资源也会加载。但这些外部资源,比如:图片、css、js 不经常发生变化,如果每次都加载这些外部资源会带来资源的浪费,而且加载时间过长也会影响用户体验。
HTTP缓存技术就是为了解决这个问题而出现的。HTTP缓存将静态资源存储在浏览器内部,下次请求相同资源时可以直接使用。
也会有一些系列的策略来保证如果资源一旦更新,缓存也要随之而更新。
一、强缓存策略
直接从本地副本比对读取,不去请求服务器,返回的状态码是 200。
强缓存可以设置静态资源有效期,如果超过有效期就认为缓存作废。
HTTP1.0
Expires
当我们请求一个资源,服务器返回时,可以在 Response Headers 中增加 Expires 字段表示资源的过期时间。
// 设置缓存过期时间为 10秒
res.setHeader('Expires', new Date(Date.now() + 10000).toUTCString())
Expires 是一个时间戳(格林尼治时间),当客户端加载资源的时候,会把客户端时间与该时间戳做对比,如果大于该时间戳则已过期,否则直接使用该缓存资源。
但是有一个很大的问题就是,客户端时间与服务器不能保证一致。
HTTP1.1
cache-control
正由于上面说的可能存在的问题,HTTP1.1 新增了 cache-control 字段来解决这个问题。
cache-control 不依赖于客户端时间。所以当 cache-control 和 expires 都存在的时候, cache-control 优先级更高。
cache-control 主要有 max-age、s-maxage、public、private、no-cache、no-store等值。
public::所有内容都将被缓存(客户端和代理服务器都可以缓存)。
private:内容只缓存到私有缓存中(客户端可以缓存)。
no-cache:需要使用协商缓存来验证缓存数据。
no-store:所有内容都不会缓存。
must-revalidation / proxy-revalidation:如果缓存的内容失效,请求必须发送到服务器 / 代理以进行重新验证。
max-age = xxx(xxx is numeric):缓存的内容将在 xxx 秒后失效,这个选项只在HTTP1.1可用,并如果和 Last-Modified 一起使用时,优先级较高。
// 设置缓存过期时间为 20秒 HTTP1.1
res.setHeader('Cache-control', 'max-age=20')
二、协商缓存
上面的 expires 和 cache-control 都会访问本地缓存直接验证看是否过期,如果没过期直接使用本地缓存,并返回200。
但如果设置了 no-cache ,则本地缓存会被忽略,会去请求服务器验证资源是否更新,如果没更新才继续使用本地缓存,此时返回的是 304,这就是协商缓存。
协商缓存包括 last-modified 和 Etag 。
协商缓存简单的说就是浏览器和服务器间就是否要使用缓存在做协商。如果协商的结果是需要更新,则返回200并返回更新内容。如果不需要更新,返回状态码304,不用返回内容,这样虽然也需要后端应答,但是后端既不需要生成内容也不需要传输内容,依然可以享受缓存的种种好处。
last-modified & if-Modified-Since
这是一组通过协商修改时间为基础的策略。
// 协商缓存
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('last-modified', new Date().toUTCString()) // 记录最后缓存时间
// 最后缓存时间10秒内,进入协商缓存
if(new Date(req.headers['if-modified-since']).getTime() + 10*1000 > Date.now()) {
res.statusCode = 304
res.end()
return
}
Etag & if-None-Match
另一种办法是通过内容判断,一般的做法是将返回内容进行摘要(Hash),然后通过对比摘要来判断内容是否更新。
const crypto = require('crypto')
// hex 将二进制编码转换成16进制字符串
const hash = crypto.createHash('sha1').update(content).digest('hex')
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('Etag', hash)
if(req.headers['if-none-match'] === hash) {
res.statusCode = 304
res.end()
return
}