响应头信息有:
- Cache-Control // 控制缓存
- Expire // 控制缓存
- ETag // 校验缓存用
- Last-Modified // 校验缓存用
Cache-Control
Cache-Control
是用来控制缓存的,比如缓存要不要开启,缓存时长是多少,要不要每次请求缓存前都需要校验等等。ETag
和 Last-Modified
是用于缓存失效后的校验,它们都需要对应的请求头一起使用。
通常 Cache-Control
会搭配 ETag
或 Last-Modified
一起使用用于更好的控制缓存,当然也可以只使用其中一个。
Expires
Expires
头信息返回的值是一个时间日期,在这个日期之后响应被视为已过期。
If there is a
Cache-Control
header with themax-age
ors-maxage
directive in the response, theExpires
header is ignored.
从这里的定义可以看到它的优先级比 Cache-Control
低,如果响应有 Cache-Control
头信息则 Expires
头信息会被忽略。
这是因为 Cache-Control
和 ETag
HTTP 1.1 新增加的响应头,Expires
和 Last-Modified
HTTP 1.1 之前的响应头。至于新加的响应头的好处其中之一是控制更精确(Last-Modified 只到秒级别)。
ETag
ETag
是响应内容某个指定版本的一个标识符,通常可以用文件的 hash 或者某个固定规则的算法,比如文章最后修改时间的 MD5 等。
它的规范如下,W 开头规范是弱校验用的,这里就不展开了
ETag: W/"<etag_value>"
ETag: "<etag_value>"
ETag
另一个典型的用法是缓存未更改的资源
- 客户端再次请求一个指定的资源地址,这个资源缓存已过期
- 客户端会通过一个
If-None-Match
请求头发送上次响应的ETag
的值 - 服务端会比较客户端发送的这个
ETag
值 和资源当前版本的ETag
值 - 如果这两个值相等则会响应一个
304 Not Modified
状态码且没有响应内容 - 客户端在接收到
304
响应后就知道上次缓存的内容还是新鲜的,可以继续使用的
Last-Modified
The
Last-Modified
response HTTP header contains the date and time at which the origin server believes the resource was last modified. It is used as a validator to determine if a resource received or stored is the same. Less accurate than anETag
header, it is a fallback mechanism. Conditional requests containingIf-Modified-Since
orIf-Unmodified-Since
headers make use of this field.
ETag and Last-Modified
ETag
和 Last-Modified
是用来校验缓存用的,可以理解成缓存的一个ID,它们都有对应请求头,If-None-Match
和 If-Modified-Since
,服务端在发出响应之前会根据请求的头信息去匹配计算,比如请求头有 If-None-Match
: 'ak1017',服务端在响应之前会计算档次响应的 ETag
和这个值相等就会响应一个 304 没有内容的响应,客户端在接收到 304 之后就会继续使用上次缓存的内容。达到节省流量,提升响应时间。
Last-modified and If-Modified-Since
this is resopone header and request header, they match each other,
ETag and If-None-Match
一个请求命中缓存会响应的状态
- 304 no data
- 200 disk cache
- 200 memory cache
示例一
下面是一个响应头同时包含 Cache-Control
和 Last-Modified
的例子
// 使用 node + http 实现的部分代码
if (req.url === '/css.css') {
res.setHeader('Cache-Control', 'max-age=160')
res.setHeader('Last-Modified', 'Wed, 23 Dec 2020 12:35:35 GMT')
res.setHeader('Content-Type', 'text/css; charset=UTF-8')
if (req.headers['if-modified-since']) {
res.statusCode = 304
res.end()
return
}
let css = fs.readFileSync('./css.css')
res.write(css)
res.end()
return
}
下面的访问测试都是基于 chrome 浏览器 87.0.4280.88
No | Name | Status | Type | Size |
---|---|---|---|---|
第一次 | css.css | 200 OK | stylesheet | 77.6 kB 77.4 kB |
第二次 | css.css | 200 OK | stylesheet | (memory cache) 77.4 kB |
第三次,缓存过期 | css.css | 304 Not Modified | stylesheet | 202 B 77.4 kB |
示例二
下面是一个响应头同时包含 Cache-Control
和 ETag
的例子
if (req.url === '/etag.css') {
let etag = 'ak307'
res.setHeader('Cache-Control', 'max-age=45')
res.setHeader('Content-Type', 'text/css; charset=UTF-8')
res.setHeader('ETag', etag)
if (req.headers['if-none-match'] === etag) {
res.statusCode = 304
res.write('here is bad')
res.end()
return
}
let css = fs.readFileSync('./css.css')
res.write(css)
res.end()
return
}
No | Name | Status | Type | Size |
---|---|---|---|---|
第一次 | etag.css | 200 OK | stylesheet | 77.6 kB 77.4 kB |
第二次 | etag.css | 200 OK | stylesheet | (memory cache) 77.4 kB |
第三次,缓存过期 | etag.css | 304 Not Modified | stylesheet | 174 B 77.4 kB |
这里的再次请求有时会遇到响应 200 但响应内容没有的情况
some | etag.css | 200 OK | stylesheet | 174 B 77.4 kB
设置 Cache-Control 后浏览器再次请求资源时候如果缓存没有过期则就直接使用缓存,在缓存过期后会向服务端发起请求,这时接收到一个 304 客户端继续使用上次的缓存内容并更新了缓存有效期。
示例三
下面是一个响应头只有 ETag
的例子
if (req.url === '/etag2.css') {
let etag = 'ak308'
res.setHeader('Content-Type', 'text/css; charset=UTF-8')
res.setHeader('ETag', etag)
if (req.headers['if-none-match'] === etag) {
res.statusCode = 304
res.write('here is bad')
res.end()
return
}
let css = fs.readFileSync('./css.css')
res.write(css)
res.end()
return
}
No | Name | Status | Type | Size |
---|---|---|---|---|
第一次 | etag2.css | 200 OK | stylesheet | 5.8 kB 5.6kB |
第二次 | etag2.css | 304 Not Modified | stylesheet | 146 B 77.4 kB |
第N次 | etag2.css | 304 Not Modified | stylesheet | 146 B 77.4 kB |
这里不设置 Cache-Control 浏览器默认也会把资源存入缓存,只是每次请求都会向远端服务器发起校验,然后接收到响应 304 继续使用上次的响应内容。
示例四
下面是一个响应头只有 Last-Modified
的例子
if (req.url === '/style-no-cache.css') {
const last = 'Wed, 23 Dec 2020 12:35:35 GMT'
// res.setHeader('Cache-Control', 'max-age=45')
res.setHeader('Last-Modified', last)
res.setHeader('Content-Type', 'text/css; charset=UTF-8')
res.removeHeader('date')
if (req.headers['if-modified-since'] === last) {
console.log('last modified enter 304')
res.statusCode = 304
res.end()
return
}
let css = fs.readFileSync('./css.css')
res.write(css)
res.end()
return
}
No | Name | Status | Type | Size |
---|---|---|---|---|
第一次 | style-no-cache.css | 200 OK | stylesheet | 77.6 kB 77.4 kB |
第二次 | style-no-cache.css | 200 OK | stylesheet | (memory cache) 77.4 kB |
第N次 | style-no-cache.css | 200 OK | stylesheet | (memory cache) 77.4 kB |
相比较只有 ETag
响应头的,这里只有 Last-Modified
响应头,再次请求都是直接使用缓存的内容,在测试中没发现资源有向远端发起校验,这里也没有设置对应的缓存时长,why?(这里只在设置的 Last-Modified
时间是已过期的情况,如果 Last-Modified
没有过期在过期之前访问还是会响应 304
,只是在后续多次访问后会变成 memory cache
)
示例五
下面是一个响应头包含 Expires
和 Last-Modified
的例子
if (req.url === '/style-expires.css') {
const last = 'Wed, 23 Dec 2020 12:35:35 GMT'
res.setHeader('Expires', 'Sun, 10 Jan 2020 09:03:00 GMT')
res.setHeader('Last-Modified', last)
res.setHeader('Content-Type', 'text/css; charset=UTF-8')
if (req.headers['if-modified-since'] === last) {
res.statusCode = 304
res.end()
return
}
let css = fs.readFileSync('./css.css')
res.write(css)
res.end()
return
}
No | Name | Status | Type | Size |
---|---|---|---|---|
第一次 | style-expires.css | 200 OK | stylesheet | 77.6 kB 77.4 kB |
第二次 | style-expires.css | 200 OK | stylesheet | (memory cache) 77.4 kB |
第三次,超过 Expires 时间 | style-expires.css | 304 Not Modified | stylesheet | 215 B 77.4 kB |
这里可以看到设置 Expires
和 Cache-Control
有同样的效果
示例六
下面是一个响应头设置 Cache-Control: no-store
和 ETag
的例子
if (req.url === '/style-no-store.css') {
let etag = 'ak857'
res.setHeader('Cache-Control', 'no-store')
res.setHeader('Content-Type', 'text/css; charset=UTF-8')
res.setHeader('ETag', etag)
if (req.headers['if-none-match'] === etag) {
res.statusCode = 304
res.write('here is bad')
res.end()
return
}
let css = fs.readFileSync('./css.css')
res.write(css)
res.end()
return
}
No | Name | Status | Type | Size |
---|---|---|---|---|
第一次 | style-no-store.css | 200 OK | stylesheet | 77.6 kB 77.4 kB |
第N次 | style-no-store.css | 200 OK | stylesheet | 77.6 kB 77.4 kB |
设置为 no-store 的资源,浏览器端就不会启用缓存,每次都成远端获取最新的内容,且请求头不会追加对应的 If-None-Match
浏览器什么情况下会把请求的资源存入缓存?
只要请求到的响应头开启缓存( Cache-Control: max-age=<number>
或 Expires
)或包含 ETag
响应头,或者即使没有设置 Cache-Control
浏览器都会把资源默认存入缓存,除非明确设置 Cache-Control: no-store
不需要浏览器存储缓存。