缓存是一种保存资源副本并在下次请求时直接使用该副本的技术,经过复用之前获取的资源,能够显着提升网站性能,下降服务器处理压力,减小了等待时间和网络流量。经过使用 HTTP缓存,变得更加响应性。
这篇文章会介绍三种缓存机制在nodejs
中的实现,分别是:html
Cache-Control/Expires
对比缓存前端
Last-Modified/If-Modified-Since
Etag/If-None-Match
强制缓存
若是生效,不须要再和服务器发生交互,对比缓存
无论是否生效,都须要与服务端发生交互。强制缓存
优先级高于对比缓存
,强制缓存
生效时,再也不执行对比缓存
规则。为了方便测试,新建一个简单的http服务进行调试:node
const http = require("http") const url = require("url") const mime = require("mime") const fs = require("fs") const server = http.createServer((req, res) => { const { pathname } = url.parse(req.url, true) const abspath = process.cwd() + pathname fs.stat(abspath, handleRequest) // 判断是文件仍是文件夹 function handleRequest(err, statObj) { if (err || statObj.isDirectory()) return sendError(err) sendFile() } // 响应错误请求 function sendError(error) { res.statusCode = 404 res.end(`Not Found \r\n ${error.toString()}`) } // 响应文件请求 function sendFile() { res.setHeader("Content-Type", mime.getType(abspath) + ";charset=utf-8") fs.createReadStream(abspath).pipe(res) } }) server.listen(3000, () => console.log("serving http://127.0.0.1:3000"))
强制缓存指的是在缓存数据未失效的状况下,能够直接使用缓存数据,浏览器经过服务器响应的header
获取缓存规则信息。对于强制缓存,响应头header使用Cache-Control/Expires
来标明失效规则。浏览器
Expires
是HTTP1.0的东西,如今默认浏览器均默认使用HTTP 1.1,因此它的做用基本忽略,咱们在响应头header设置Expires
,浏览器根据它的到期时间来决定是否使用缓存数据:缓存
res.setHeader("Expries",new Date(Date.now()+5*1000).toUTCString());
Cache-Control
是最重要的规则。常见的取值有private、public、no-cache、max-age,no-store,默认为private。服务器
对比缓存
来验证缓存数据强制缓存
,对比缓存
都不会触发能够在handleRequest
方法中添加给响应头设置Cache-Control
,在浏览器刷新查看效果:网络
function handleRequest(err, statObj) { ... res.setHeader("Cache-Control","max-age=10"); sendFile() }
若是常常调试前端项目的开发人员,常常会把控制台Disable cache
给勾上,这里记得必定要关掉它:性能
不出意外的话,能够在Network
的请求中看到信息:测试
Status Code: 200 OK (from disk cache)
对比缓存,服务器将文件的修改信息发送给客户端(浏览器),客户端在下次请求的时候顺便带上,而后服务端就能够拿到上一次的修改信息,跟本地的文件修改信息作比较,告诉客户端是否用缓存数据仍是用最新数据了网站
经过statObj
的ctime
属性能够获取文件的修改时间,将这个修改信息经过请求头Last-Modified
属性发送给浏览器:
const serverTime = statObj.ctime.toUTCString() res.setHeader("Last-Modified", serverTime)
下次客户端请求的时候,也会在请求头经过if-modified-since
带上:
const clientTime = req.headers["if-modified-since"]
修改handleRequest
方法以下:
function handleRequest(err, statObj) { if (err || statObj.isDirectory()) return sendError(err) const clientTime = req.headers["if-modified-since"] const serverTime = statObj.ctime.toUTCString() // 若是本地的文件修改时间和浏览器返回的修改时间相同,则使用缓存的数据,返回304 if (clientTime === serverTime) { res.statusCode = 304 return res.end() } res.setHeader("Last-Modified", serverTime) res.setHeader("Cache-Control", "no-cache") // 对比缓存验证缓存数据 sendFile() }
不过这种方式有两个弊端:
上面提到的两个弊端,能够经过Etag/If-None-Match
方式解决,也就是内容对比,不过Etag
生成有必定的开销,若是文件频繁变化对服务器有额外压力。
固然咱们不可能将内容都存在header
里面,这里能够经过crypto
将文件内容加密成一串秘钥,写在header
的Etag
属性中:
const crypto = require("crypto"); ... const md5 = crypto.createHash("md5"); // md5加密 const rs = fs.createReadStream(abspath); // 建立可读流 const data = []; rs.on("data",(buffer)=>{ md5.update(buffer); // 读取文件内容过程当中加密 data.push(buffer); }); rs.on("end",()=>{ const serverMatch = md5.digest("base64"); // 加密后的文件 const clientMatch = req.headers["if-none-match"]; // 客户端下次请求会带上serverMatch if(clientMatch === serverMatch){ // 对比文件内容是否相同 res.statusCode = 304; return res.end(null); } // 设置 ETag res.setHeader("ETag", serverMatch) res.end(Buffer.concat(data)); })
咱们能够在业务中根据本身的须要,设置对应的缓存方式,这里经过写个通用方法,将这三种模式整合起来:
function cache(statObj) { // 强制缓存 res.setHeader("Cache-Control", "max-age=60") // 时间对比 let lastModified = statObj.ctime.toUTCString() let modifiedSince = req.headers["if-modified-since"] res.setHeader("Last-Modified", lastModified) if (modifiedSince !== lastModified) return false // 内容对比 let etag = statObj.size + "" let noneMatch = req.headers["if-none-match"] res.setHeader("ETag", etag) if (etag !== noneMatch) return false return true } ... if(cache(statObj)){ res.statusCode = 304 return res.end(null) }