学习整理了 web 缓存的一些策略, 如有不正确的地方, 欢迎指正
浏览器端的缓存规则
缓存行为主要由缓存策略决定, 而缓存策略由内容拥有者设置这些策略主要通过特定的 HTTP 头部来清晰地表达
当一个用户发起一个静态资源请求的时候, 浏览器会通过以下几步来获取资源:
本地缓存阶段: 先在本地查找该资源, 如果有发现该资源, 而且该资源还没有过期, 就使用这一个资源, 完全不会发送 http 请求到服务器;
协商缓存阶段: 如果在本地缓存找到对应的资源, 但是不知道该资源是否过期或者已经过期, 则发一个 http 请求到服务器, 然后服务器判断这个请求, 如果请求的资源在服务器上没有改动过, 则返回 304, 让浏览器使用本地找到的那个资源;
缓存失败阶段: 当服务器发现请求的资源已经修改过, 或者这是一个新的请求 (在本来没有找到资源), 服务器则返回该资源的数据, 并且返回 200, 当然这个是指找到资源的情况下, 如果服务器上没有这个资源, 则返回 404
本地缓存
可以通过设置请求头来进行配置
- Expires
- res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toUTCString())
- Cache Control
- res.setHeader('Cache-Control', 'max-age=30')
注意: Cache-Control: 这个是 http 1.1 中为了弥补 Expires 缺陷新加入的如果设了 max-age,max-age 就会覆盖 expires
完整代码
- http.createServer(function(req, res) {
- let { pathname } = url.parse(req.url, true)
- let filepath = path.join(__dirname, pathname)
- fs.stat(filepath, (err, stat) => {
- if (err) {
- sendError(req, res)
- } else {
- send(req, res, filepath)
- }
- })
- }).listen(8080)
- function sendError(req, res) {
- res.statusCode = 404
- res.end('Not Found')
- }
- function send(req, res, filepath) {
- res.setHeader('Content-Type', mime.getType(filepath))
- res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toUTCString())
- res.setHeader('Cache-Control', 'max-age=30')
- fs.createReadStream(filepath).pipe(res)
- }
协商缓存
Last-Modified & if-modified-since
Last-Modified 与 If-Modified-Since 是一对报文头, 属于 http 1.0
last-modified 是 WEB 服务器认为对象的最后修改时间, 比如文件的最后修改时间, 动态页面的最后产生时间
- http.createServer(function(req, res) {
- let { pathname } = url.parse(req.url, true)
- let filepath = path.join(__dirname, pathname)
- fs.stat(filepath, (err, stat) => {
- if (err) {
- res.statusCode = 404
- res.end('Not Found')
- } else {
- let ifModifiedSince = req.headers['if-modified-since']
- let LastModified = stat.ctime.toGMTString()
- if (ifModifiedSince === LastModified) {
- res.statusCode = 304
- res.end()
- } else {
- send(req, res, filepath, stat)
- }
- }
- })
- }).listen(8080)
- function send(req, res, filepath, stat) {
- res.setHeader('Content-Type', mime.getType())
- res.setHeader('Last-Modified', stat.ctime.toGMTString())
- fs.createReadStream(filepath).pipe(res)
- }
- ETag & If-None-Match
ETag 与 If-None-Match 是一对报文, 属于 http 1.1
ETag 可以用来解决这种问题 ETag 是一个文件的唯一标志符就像一个哈希或者指纹, 每个文件都有一个单独的标志, 只要这个文件发生了改变, 这个标志就会发生变化
ETag 机制类似于乐观锁机制, 如果请求报文的 ETag 与服务器的不一致, 则表示该资源已经被修改过来, 需要发最新的内容给浏览器
同时使用这两个报文头, 在完全匹配 If-Modified-Since 和 If-None-Match 即检查完修改时间和 Etag 之后, 如都与服务器的相符, 服务器返回 304, 否则, 发送最新内容给浏览器
- http.createServer(function(req, res) {
- let { pathname } = url.parse(req.url, true)
- let filepath = path.join(__dirname, pathname)
- fs.stat(filepath, (err, stat) => {
- if (err) {
- sendError(req, res)
- } else {
- let ifNoneMatch = req.headers['if-none-match']
- let out = fs.createReadStream(filepath)
- let md5 = crypto.createHash('md5')
- out.on('data', function(data) {
- md5.update(data)
- })
- out.on('end', function() {
- let etag = md5.digest('hex')
- if (ifNoneMatch === etag) {
- res.statusCode = 304
- res.end()
- } else {
- send(req, res, filepath, etag)
- }
- })
- }
- })
- }).listen(8080)
- function sendError(req, res) {
- res.statusCode('404')
- res.end('Not Found')
- }
- function send(req, res, filepath, etag) {
- res.setHeader('Content-Type', mime.getType())
- res.setHeader('ETag', etag)
- fs.createReadStream(filepath).pipe(res)
- }
Etag/lastModified 过程如下:
客户端请求一个页面 (A)
服务器返回页面 A, 并在给 A 加上一个 Last-Modified/ETag
客户端展现该页面, 并将页面连同 Last-Modified/ETag 一起缓存
客户再次请求页面 A, 并将上次请求时服务器返回的 Last-Modified/ETag 一起传递给服务器
服务器检查该 Last-Modified 或 ETag, 并判断出该页面自上次客户端请求之后还未被修改, 直接返回响应 304 和一个空的响应体
服务器端缓存
CND 缓存
CDN 缓存, 也叫网关缓存反向代理缓存浏览器先向 CDN 网关发起 WEB 请求, 网关服务器后面对应着一台或多台负载均衡源服务器, 会根据它们的负载请求, 动态地请求转发到合适的源服务器上
来源: https://juejin.im/post/5a92af886fb9a063317c7122