概念
缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。——MDN
缓存能缓解服务器压力,提高响应速度,提升用户体验。
以下讨论的缓存是针对对img/script/css资源而言的,且缓存策略都是依靠 http 报文的首部来实现。
实验
搭建实验环境
编写 html 文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="icon" href="data:;base64,=">
<title>缓存</title>
</head>
<body>
<img style="width:400px;" src="sun.jpg">
<div>
缓存实验
</div>
</body>
</html>
服务端代码
const path = require('path')
const url = require('url')
const fs = require('fs')
const zlib = require('zlib')
const img = fs.readFileSync(path.resolve(__dirname, './static/sun.jpg'), 'binary')
http.createServer((req, res) => {
const html = fs.createReadStream(path.resolve(__dirname, './static/index.html'))
let {
pathname
} = url.parse(req.url)
if (pathname === '/') {
res.writeHead(200, {
'Content-Type': 'text/html',
'Content-Encoding': 'gzip'
})
html.pipe(zlib.createGzip()).pipe(res)
} else if (pathname === '/sun.jpg') {
res.writeHead(200, {
'Content-Type': 'image/jpeg'
})
res.write(img, 'binary')
res.end()
} else {
res.end(pathname)
}
}).listen(3210)
访问 http://localhost:3210 , 看到相应的文档和图片,且无论刷新多少次,sun.jpg 的 Status 和 Size 都不变。控制台里勾选 Preserve log
Cache-Control
通过在服务端设置相应首部 Cache-Control 可以控制缓存行为,例如
- Cache-Control: no-cache, 告诉浏览器,下次请求该资源时,不直接使用缓存,而是向服务端发送请求,服务端会根据请求,判断本地的资源是否过期
- Cache-Control: no-store, 告诉浏览器,不要缓存该资源
- Cache-Control: max-age=age, 告诉浏览器,这个资源有效的时长 age,在该时间范围内,如果需要该资源,直接从本地取,不要烦我(向我发送请求)
修改服务端代码
设置图片缓存时长 30s
res.writeHead(200, {
'Content-Type': 'image/jpeg',
'Cache-Control': 'max-age=30'
})
快速刷新页面2次,超过30s后,再刷新一次
三次刷新结果在控制台显示的情况如下
其中第二次请求 sun.jpg 的请求情况如下,200 OK (from memory cache),第三次请求 sun.jpg 则又是从服务端获取图片,显示资源 1.6M。
Last-Modified & If-Modified
现在的情形是这样的,30s到了,浏览器向服务端发出 sun.jpg 的请求,但服务器并没有更新该资源,所以服务器告诉浏览器,“我没有修改该资源,你还是用你本地的吧,我就不再发给你了”。这些通过 Last-Modified 和 If-Modified-Since 来实现。
浏览器第一次向服务端请求 sun.jpg,服务端不仅返回 sun.jpg,还通过响应首部的 Last-Modified(浏览器自动添加),告诉浏览器该资源最后修改的时间。
下次浏览器再次请求 sun.jpg,会通过请求首部的 If-Modified-Since, 把自己手上这个资源最后修改的时间告诉服务端,服务端通过这个时间,判断浏览器本地的资源是否是最新的,若是,则返回 304(not modified) 和响应头部即可,不用返回图片;若不是,则返回 200 和图片。
修改服务端代码
const http = require('http')
const path = require('path')
const url = require('url')
const fs = require('fs')
const zlib = require('zlib')
const img = fs.readFileSync(path.resolve(__dirname, './static/sun.jpg'), 'binary')
let date = new Date()
let lastModified = date.toUTCString()
http.createServer((req, res) => {
const html = fs.createReadStream(path.resolve(__dirname, './static/index.html'))
let {
pathname
} = url.parse(req.url)
if (pathname === '/') {
res.writeHead(200, {
'Content-Type': 'text/html',
'Content-Encoding': 'gzip'
})
html.pipe(zlib.createGzip()).pipe(res)
} else if (pathname === '/sun.jpg') {
if (req.headers['if-modified-since'] === lastModified) {
res.writeHead(304, {
'Content-Type': 'image/jpeg',
'Cache-Control': 'max-age=30',
'Last-Modified': lastModified
})
res.end()
} else {
res.writeHead(200, {
'Content-Type': 'image/jpeg',
'Cache-Control': 'max-age=30',
'Last-Modified': lastModified
})
res.write(img, 'binary')
res.end()
}
} else {
res.end(pathname)
}
}).listen(3210)
浏览器刷新一次,30s内,再刷新一次,30s后再刷新一次
三次刷新的结果在控制台里显示如下,最后一次还有 189B 是服务端响应头部的大小,178ms 里包含了服务端比较修改时间,返回响应头部的时间。
且最后一次请求 sun.jpg 的请求情形如下
ETag & If-None-Match
Etag 和 If-None-Match 起到的作用和上文中 Last-Modified 和 If-Modified 差不多,区别在于 Etag 是服务端上通过算法给资源计算出的唯一标示,当资源修改时,该标示会发生变化。服务端通过响应首部 Etag 将该标示告诉浏览器,浏览器再下一次请求该资源时,会通过请求首部 If-None-Match 带上该标示,服务端会比较请求中的标示和该资源最新的标示,如果一样,证明浏览器拥有的该资源是最新的,仅返回304和响应头部,否则返回200和整个资源。
强制刷新
常使用的强制刷新就是通过修改请求首部来实现的
将 Cache-Control 和 Pragma 设为 no-cache,去除 If-Modified, If-None-Match
盗个图,HTTP缓存控制小结
快速更新
很多js文件或css文件的文件名会带有文件指纹或版本号,通过改变文件名,让浏览器以为请求的新的未缓存的资源从而实现快速更新。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。