浅谈HTTP 缓存的那些事儿(3)

通过两图的对比,我们可以发现,协商缓存生效时的状态码为 304,并且报文大小和请求时间大大减少,原因是服务端在进行标识比对后只返回了 header 部分,通过状态码来通知浏览器使用缓存,不再需要将报文主体部分一起返回给浏览器。

4、NodeJS 服务器实现协商缓存

// 协商缓存 const http = require("http"); const url = require("url"); const path = require("path"); const mime = require("mime"); const fs = require("fs");0 const crytpo = require("crytpo"); const server = http.createServer((req, res) => { let { pathname } = url.parse(req.url, true); pathname = pathname !== "https://www.jb51.net/" ? pathname : "/index.html"; // 获取读取文件的绝对路径 let p = path.join(__dirname, pathname); // 查看路径是否合法 fs.stat(p, (err, statObj) => { // 路径不合法则直接中断连接 if (err) return res.end("Not Found"); let md5 = crypto.createHash("md5"); // 创建加密的转换流 let rs = fs.createReadStream(p); // 创建可读流 // 读取文件内容并加密 rs.on("data", data => md5.update(data)); rs.on("end", () => { let ctime = statObj.ctime.toGMTString(); // 获取文件最后修改时间 let flag = md5.digest("hex"); // 获取加密后的唯一标识 // 获取协商缓存的请求头 let ifModifiedSince = req.headers["if-modified-since"]; let ifNoneMatch = req.headers["if-none-match"]; if (ifModifiedSince === ctime || ifNoneMatch === flag) { res.statusCode = 304; res.end(); } else { // 设置协商缓存 res.setHeader("Last-Modified", ctime); res.setHeader("Etag", flag); // 设置文件类型并响应给浏览器 res.setHeader("Content-Type", `${mime.getType(p)};charset=utf8`); rs.pipe(res); } }); }); }); server.listen(3000, () => { console.log("server start 3000"); });

在上面的代码中是通过可读流读取文件内容,并通过 crypto 模块进行了 md5 加密后的结果作为了唯一标识,这样就能保证只要文件内容不变,就会命中缓存,其中兼容了 HTTP 1.0 和 HTTP 1.1 两个版本,只要满足一个则直接返回 304 通知浏览器命中缓存。

注意:其实读取文件内容加密这种做法并不可取,假如读取的是大文件,在读取文件内容和进行 md5 加密这个过程会非常消耗时间,所以在开发中要针对业务的实际情况选择可以保证服务器性能的方式生成唯一标识,比如根据文件的摘要。

总结

为了使缓存策略更加健壮、灵活,HTTP 1.0 版本 和 HTTP 1.1 版本的缓存策略会同时使用,甚至强制缓存和协商缓存也会同时使用,对于强制缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接使用缓存,超出有效时间,执行协商缓存策略,对于协商缓存,将缓存信息中的 Etag 和 Last-Modified 通过请求头 If-None-Match 和 If-Modified-Since 发送给服务器,由服务器校验同时设置新的强制缓存,校验通过并返回 304 状态码时,浏览器直接使用缓存,如果协商缓存也未命中,则服务器重新设置协商缓存的标识。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/96bc6cd48d0c13a731f0ee39063f5d69.html