通过Node.js小例子了解浏览器缓存策略缓存的几个阶段1.强缓存策略浏览器发起请求后,不会直接向服务器请求数据。会先直接到达强缓存阶段。如果强缓存命中,则直接返回。如果没有命中,则进入下一阶段协商缓存策略。2、协商缓存策略协商缓存在强缓存未命中或按F5键刷新页面时触发。每次都会带上这个标志跟服务器核对。如果匹配,则返回一个304标识,表示该资源没有更新。如果协商缓存也失效,则进入下一阶段获取最新的数据,返回状态码为200。3.存储策略当强缓存->协商缓存失效时,请求会直接到达服务器去获取最新资源,设置缓存策略,返回。2.强缓存强缓存的实现分为Expires和Cache-Control。ExpiresExpires是HTTP1.0时期的产物,在响应中设置。示例如下:response.writeHead(200,{'Content-Type':'text/javascript','Expires':newDate('2020-03-2511:19:00'),});设置成功运行nodeexpires.js,在ResponseHeaders中可以看到如下信息:Expires:WedMar25202011:19:00GMT+0800(GMT+08:00)刷新页面两次,可以看到次级尺寸栏返回内存缓存,此时Expires缓存命中。expires是参考本地时间,如果本地时间被修改,缓存可能会失效。Cache-ControlCache-Control是HTTP1.1时代的产物,可以在请求头或者响应头中设置。它的值包括以下选项:1.Cacheabilitypublic:任何地方都可以缓存httppasses(代理服务器也可以Cacheable)private:只有发起请求的浏览器才能缓存。如果设置了代理缓存,则代理缓存不会生效。no-cache:没有节点可以缓存(bypassstrongcache,但还是会协商缓存)2.Expirationmax-age=:设置缓存多少秒过期s-maxage=:会代替max-age,并且只会在代理服务器(nginx代理服务器)上生效请求者发起的header,意味着即使缓存过期,过期的缓存在max-stale期限内仍然可以使用,不需要向服务器请求新的内容.3.revalidatemust-revalidate:如果max-staleage设置的内容已经过期,必须请求服务器重新获取数据来验证内容是否过期proxy-revalidate:主要用于缓存服务器,指定cacheserver过期后重新从原服务器获取,不能从本地获取4.Otherno-store:本地和代理服务器都不能存储这个缓存,一直使用从服务器获取body的新内容(强缓存,协商缓存不会通过)no-transform:主要用于代理服务器,告诉代理服务器不要随意更改返回的内容Cache-Control示例先思考两个问题?页面中引入静态资源文件,为什么改静态资源文件后请求还是和之前一样的内容?在使用一些打包工具比如webpack的时候,为什么要加一串hash码呢?缓存控制.html
缓存控制脚本>正文>cache-control.js浏览器输入http://localhost:3010/加载cache-control.html文件,会请求http://localhost:3010/script.js,如果url等于/script。js,设置cache-control的max-age用于浏览器缓存consthttp=require('http');constfs=require('fs');constport=3010;http.createServer((request,response)=>{console.log('requesturl:',request.url);if(request.url==='/'){consthtml=fs.readFileSync('./example/cache/cache-control.html','utf-8');response.writeHead(200,{'Content-Type':'text/html',});response.end(html);}elseif(request.url==='/script.js'){response.writeHead(200,{'Content-Type':'text/javascript','Cache-Control':'max-age=200'});response.end("console.log('scriptload')");}}).listen(port);console.log('serverlisteningonport',港口);第一次运行浏览器没有问题,控制台运行结果正常响应是修改cache-control.js的返回值...response.writeHead(200,{'Content-Type':'text/javascript','Cache-Control':'max-age=200'});response.end("console.log('scriptload!!!')");...中断最后一个程序,secondtime运行浏览器第二次运行结果,从内存cahce中读取,浏览器控制台不打印修改内容,控制台运行结果只请求/不请求/script.js源码参考:github.com/Q-Angelo/http-protocol/blob/master/example/cache/cache-control.js浏览器没有返回服务端修改后的结果给我们,就是为什么?先回答第一个问题。在页面中引入一个静态资源文件。为什么更改静态资源文件后请求会重新发起?就是因为我们请求的url/script.js没有变化,所以浏览器不会经过服务端的验证,会直接从客户端缓存中读取,这样就会出现问题。我们的js静态资源更新后,并不会马上更新到我们的客户端,这在前端开发中也很常见。一个问题,我们希望浏览器缓存我们的静态资源文件(js、css、img等),不希望客户端在服务端内容更新后,仍然请求缓存的资源。回答第二个问题,使用webpack等一些打包工具为什么要加一串hash码?解决方法是我们在做js构建过程的时候,根据其内容的hash值在打包后的js文件的名称中加上一串hash码,这样你的js文件或者css文件的内容就保持不变,所以生成的hashcode是不会变的,页面上会体现你的url没有变。如果您的文件内容发生变化,嵌入到页面中的文件的url也会发生变化。这样就可以达到更新缓存的目的,这也是前端常见的静态资源方案。Expires和Cache-Control与HTTP协议相比:Expires是HTTP1.0时代的产物,Cache-Control是HTTP1.1时代的产物。优先级比较:如果同时使用Cache-Control的max-age和Expires,max-age优先级会更高,Expires缓存单元会被忽略:Expires和Cache-Control的缓存单元都是基于时间,如果我想根据文件内容判断缓存是否失效怎么办?需要以下协商缓存。3、协商缓存如果强缓存未命中或者用户按F5强制刷新后进入协商缓存,服务器会根据浏览器请求时的标识进行判断。如果协商缓存生效,则返回304,否则返回200。协商缓存的实现也是基于两点,Last-Modified和ETag,需要在HTTPHeaders中设置。Last-Modified/If-Modified-SinceLast-Modified设置在服务器端响应,If-Modified-Since在浏览器端根据服务器端上次在ResponseHeaders中设置的Last-Modified值设置,如果有一个请求时,设置它的RequestHeaders值If-Modified-Since并发送给服务器,服务器也会得到这个值进行比较。下面是核心实现。if(request.url==='/script.js'){constfilePath=path.join(__dirname,request.url);//加入当前脚本文件地址conststat=fs.statSync(filePath);//获取当前脚本状态constmtime=stat.mtime.toGMTString()//文件最后修改时间constrequestMtime=request.headers['if-modified-since'];//从浏览器传来的值console.log(stat);console.log(mtime,requestMtime);//协商缓存if(mtime===requestMtime){response.statusCode=304;response.end();return;}//协商缓存失效,重新读取数据设置Last-修改响应头console.log('协商缓存Last-Modified失效');response.writeHead(200,{'Content-Type':'text/javascript','Last-Modified':mtime,});constreadStream=fs.createReadStream(filePath);readStream.pipe(response);}执行节点last-modified.js启动程序,浏览器执行http://localhost:3010/打开页面。调用了很多次,发现第一次是从服务器获取数据并且状态是200,之后每次都是内存缓存。为什么不是304?源码地址github.com/Q-Angelo/http-protocol/tree/master/example/cache/last-modified显然是强缓存生效了。你可能会觉得我没有设置强缓存??这是因为浏览器默认启用了启发式缓存,即设置了Last-Modified响应标头且未设置Cache-Control:max-age/s-maxage或Expires时。将被触发,其缓存时间之一为1,值为Date-Last-Modified0%作为缓存时间现在我们要实现304的效果,不加强缓存,直接去协商缓存,修改我们的response,设置Cache-Control=max-age=0,修改如下:response.writeHead(200,{'Content-Type':'text/javascript','Last-Modified':mtime,'Cache-Control':'max-age=0',//修改地方});再次运行我们的程序,控制台执行nodelast-modified-max-age.js再次重新打开页面查看效果,第二次直接进入协商缓存,RequestHeaders携带If-Modified-Since:Wed,25Mar202012:31:58GMT源地址https://github.com/Q-Angelo/http-protocol/tree/master/example/cache/last-modified-max-ageETag和If-None-MatchLast-Modified是根据文件的修改时间判断的,而Etag是根据文件内容是否修改来判断的,如果Etag被修改过,重新获取新的资源并返回,如果没有修改,返回304通知客户端使用本地缓存。Etag的判断主要是通过服务端的一个Hash算法来实现的。核心实现如下:if(request.url==='/script.js'){constfilePath=path.join(__dirname,request.url);//拼接当前脚本文件地址constbuffer=fs.readFileSync(filePath);//获取当前脚本状态constfileMd5=md5(buffer);//文件的md5值constnoneMatch=request.headers['if-none-match'];//浏览器传来的值if(noneMatch===fileMd5){response.statusCode=304;response.end();return;}console.log('Etag缓存失效');response.writeHead(200,{'Content-Type':'text/javascript','Cache-Control':'max-age=0','ETag':fileMd5,});constreadStream=fs.createReadStream(filePath);readStream.pipe(response);}源码地址github.com/Q-Angelo/http-protocol/tree/master/example/cache/etagnodeetag.js运行我们的程序,打开我们的页面多次访问,第二次会看到浏览器会带一个If-None-MatchHeader被传递给服务器进行验证。当前协商缓存命中,所以响应状态为304Last-Modified与Etag比较精度:Last-Modified是时间(秒),如果1秒内文件过多,一旦修改,不会在Last-下失效修改缓存策略。Etag是内容的Hash比较。只要内容发生变化,Etag就会发生变化,准确率更高。分布式部署问题:分布式部署必然涉及到负载均衡,导致Last-Modified时间可能不一致的现象,而Etag只需要保证每台机器的Hash算法一致即可保证一致性。性能消耗:Etag需要读取文件进行Hash计算,相比Last-Modified有性能损耗。Priority:如果Last-Modified/Etag同时设置,Etag的优先级会更高。相同点:校验返回304通知客户端使用本地缓存,校验失败则重新获取最新资源,设置Last-Modified/Etag响应头,返回状态码200。问题?POST可以缓存吗?GET是一种幂等操作,通常用于缓存。POST是一种非幂等操作。每次创建新资源时,不会自动处理POST请求进行缓存。请参阅rfc2616-sec9。html#sec9.1参考http://verymuch.site/2018/10/09/web-browser-cache-strategy/本文转载自微信公众号《Nodejs技术栈》,可通过以下二维码关注.转载本文请联系Nodejs技术栈公众号。