当前位置: 首页 > 后端技术 > Node.js

使用代码练习Web缓存

时间:2023-04-03 19:42:27 Node.js

Web缓存是自动保存常见文档副本的HTTP设备。当Web请求到达缓存时,如果本地有“缓存副本”,则可以从本地存储设备而不是源服务器获取文档。以上是《HTTP权威指南》中Web缓存的定义。缓存的主要好处有:减少冗余数据的传输;减少客户端的网络请求,减轻源站压力;加载速度更快。综上所述,省流量,省带宽,速度快。那么缓存是如何工作的呢?客户端和服务端如何协调缓存的时效性?下面我们通过代码一步步揭示缓存的工作原理。1.浏览器缓存当我们在浏览器地址栏输入localhost:8080/test.txt回车后,我们向指定的服务器发起对text.txt文件的请求,服务器接收到请求,找到这个文件并准备好返回给客户端,通过设置Cache-Control和Expires这两个响应头告诉客户端这个文件要缓存,过期前不要问我。首先我们看一下项目目录:|--Cache|--index.js|--assets|--index.html|--test.txt具体实现代码如下:...test.txt...//index.jsconsthttp=require('http');constpath=require('path');constfs=require('fs');http.createServer((req,res)=>{constrequestUrl=path.join(__dirname,'/assets',path.normalize(req.url));fs.stat(requestUrl,(err,stats)=>{if(err||!stats.isFile){res.writeHead(404,'NotFound');res.end();}else{constreadStream=fs.createReadStream(requestUrl);constmaxAge=10;constexpireDate=newDate(newDate().getTime()+maxAge*1000).toUTCString();res.setHeader('Cache-Control',`max-age=${maxAge},public`);res.setHeader('Expires',expireDate);readStream.pipe(res);}});}).listen(8080);缓存内容rol和Expires这两个响应头是什么意思?Cache-Control:max-age=500表示缓存存储的最大周期设置为500秒。过了这个时间,缓存就被认为过期了。格林威治标准时间:48在此日期之后文档将过期。启动服务器后,在浏览器中访问localhost:8080/index.html。这是第一次访问,没有缓存,所以服务器返回完整的资源。我们点击超链接访问test.txt:因为是第一次访问,所以没有缓存。这时候,我们点击返回按钮,返回到index.html:发现不同了吗?这时NetWork中的Size已经变成了diskcache,说明命中了浏览器缓存,也就是强缓存。此时点击超链接即可访问test.txt。如果在设置的过期时间10s以内,可以看到命中浏览Server缓存,如果超过10s,会重新从服务器获取资源。这里有一点,浏览器的前进和后退按钮总是会从缓存中读取资源,不管设置的缓存规则如何。也就是说,如果我通过浏览器的后退按钮从localhost:8080/test.txt页面返回到localhost:8080/index.html页面,我会发现无论网络多长时间都是磁盘缓存,我也是点击浏览器前进按钮进入localhost:8080/test.txt页面,即使超过了设置的过期时间,还是从磁盘缓存中。注意:Cache-Control的优先级高于Expires。由于时间差异以及服务器时间和客户端时间可能不一致,Expires可能无法准确判断缓存的有效性。但是Expires是兼容http1.0的,Cache-Control是兼容http1.1的,所以一般都设置。2、协商缓存我们在上面设置了缓存时限后,如果缓存过期了怎么办?你可能会说,资源过期了再从服务器上取。但是也有可能缓存时间已经过期,但是资源并没有发生变化,所以我们不得不引入其他的策略来应对这种情况,即协商缓存也是一种弱缓存。我们来梳理一下协商缓存的过程:服务器第一次返回一个资源时,除了设置Cache-Control和Expires响应头之外,还会设置Last-Modified(资源更新时间)和ETag(资源摘要或资源版本)两个响应头,分别代表资源的最后更改时间和实体标签。当客户端没有命中强缓存时,会再次向服务端发起请求,并携带两个请求头If-modified-Since和If-None-Match,服务端会拿到这两个请求头和之前设置的Last-Modified与ETag进行比较。如果它们不匹配,则意味着缓存不可用并返回资源。否则,表示缓存有效,返回304响应码,告知缓存可以继续使用,并更新缓存有效时间。下面我们看一下工具代码实际:consthttp=require('http');constpath=require('path');constfs=require('fs');constcrypto=require('crypto');//生成entitydigestfunctiongenerateDigest(requestUrl){lethash='2jmj7l5rSw0yVb/vlWAYkK/YBwk';让len=0;fs.readFile(requestUrl,(err,data)=>{if(err){console.error(error);thrownewError(err);}else{len=Buffer.byteLength(data,'utf8');hash=crypto.createHash('sha1').update(data,'utf-8').digest('base64').substring(0,27);}});return'"'+len.toString(16)+'-'+hash+'"';}//响应文件functionresponseFile(requestUrl,stats,res){constreadStream=fs.createReadStream(requestUrl);constmaxAge=10;constexpireDate=newDate(newDate().getTime()+maxAge*1000).toUTCString();res.setHeader('缓存控制',`max-age=${maxAge},公开`);res.setHeader('Expires',expireDate);res.setHeader('Last-Modified',stats.mtime);res.setHeader('ETag',generateDigest(requestUrl));readStream.pipe(res);}//判断新鲜度函数isFresh(requestUrl,stats,req){constifModifiedSince=req.headers['if-modified-since'];constifNoneMatch=req.headers['if-none-match'];if(!ifModifiedSince&&!ifNoneMatch){//如果没有对应的请求头,则返回一个新的资源returnfalse;}elseif(ifNoneMatch&&ifNoneMatch!==generateDigest(requestUrl)){//如果ETag不匹配(资源内容改变),说明缓存不新鲜returnfalse;}elseif(ifModifiedSince&&ifModifiedSince!==stats.mtime.toString()){//如果资源更新时间不匹配,说明缓存不新鲜returnfalse;}返回真;}http.createServer((req,res)=>{constrequestUrl=path.join(__dirname,'/assets',path.normalize(req.url));fs.stat(requestUrl,(err,stats)=>{if(err||!stats.isFile){res.writeHead(404,'未找到');重发();}else{if(isFresh(requestUrl,stats,req)){//缓存是新鲜的,告诉客户端没有可用的缓存,没有返回响应实体res.writeHead(304,'NotModified');重发();}else{//缓存不新鲜,返回资源responseFile(requestUrl,stats,res);}}});}).listen(8080);从代码中可以看出,ETag和Last-Modified都是用来校验协商缓存的。ETag基于实体标签,一般可以通过版本号或者资源摘要来指定;Last-Modified基于资源的最后修改时间。访问localhost:8080/test.txt文件时,命中强缓存后,等待10秒,再次访问,服务器返回304而不是200,说明协商缓存生效。此时修改test.txt文件,再次访问,服务器返回200,页面显示最新的test.txt文件内容。总结一下:ETag可以更准确的判断一个资源是否发生变化,比Last-Modified具有更高的优先级;基于摘要实现的ETag比较慢,占用资源较多;Last-Modified精确到秒,对于亚秒级资源更新的缓存新鲜度判断无能为力;ETag兼容http1.1,Last-Modified兼容http1.0。注意:本文之所以通过超链接访问test.txt,是因为如果直接在地址栏访问资源,浏览器会在请求头中设置cache-control:max-age=0,这样浏览器缓存永远不会被命中。本文测试的浏览器:Chrome版本88.0.4324.192参考:《HTTP权威指南》HTTP缓存etag