HTTP缓存机制
- HTTP缓存原理
- HTTP缓存策略
- nodejs实现http缓存策略demo
- Express配置缓存
一、HTTP缓存原理
1.1什么是HTTP缓存?
当客户端向原始服务器请求过一次资源以后,该资源就会被网络节点上的某个缓存设备缓存下来,当再次请求该资源时就可以直接在最近的缓存设备获取。
在web缓存中大概可以分为:数据库缓存、服务器缓存(代理服务器缓存、CDN缓存)、浏览器缓存。
浏览器缓存又可以分为:HTTP缓存、indexDB、cookie、localstorage等。
1.2公有缓存与私有缓存:
缓存可以是单个用户专用,也可以是很多用户共享,专用缓存被称为私有缓存,共享缓存被称为公有缓存。像浏览器中的HTTP缓存可以被看作是私有缓存,而CDN缓存可以被看作是公有缓存(CDN跟浏览器一样支持多种缓存技术,其中也包括HTTP缓存)。
私有缓存:
公有缓存:
大多数情况下,私有缓存与公有缓存并不是孤立存在,HTTP缓存采用的是层级结构,当一个请求发送出去以后会逐级检查是有需要的数据,当匹配到需要的数据就可以在当前缓存中直接将数据返回给客户端。
1.4缓存代理层次结构与网状缓存代理结构:
在实际中,私有缓存和公有缓存并不是单独存在,而是会被串联在整个资源请求链上,请求会在最近的缓存中获取所需要的数据,直到请求穿过所有缓存代理就会直接从原始服务器上获取数据。HTTP不支持网状缓存,但是可以通过一些协议对HTTP进行扩展,比如因特网协议ICP和超文本缓存协议HTCP,从而可以实现网状缓存。所谓的网状缓存就是指资源可以从兄弟缓存中获取,而不不单纯的层级请求获取的方式。
HTTP缓存代理层次结构:
1.5缓存命中和未命中:
缓存命中与未命中简单的说就是请求获取的数据是否是从缓存中获取的,如果是就表示命中缓存,如果不是从缓存中获取而是从原始服务器获取的数据则表示未命中。但是在http协议中并没有提供检测数据来源的相应功能,但是监视缓存的命中率对于系统来说非常重要,这里关系到系统的架构问题,就不太深入探究。
缓存命中与否在商业代理缓存中会有via首部将请求的每个传输环节的主机信息记录下来,如果使用了商用代理缓存就可以根据via来计算缓存命中,甚至可以追踪到每个缓存节点的命中率,这对于监测缓存命中率非常友好,关于via首部相关内容可以参考这边博客:https://blog.csdn.net/qq_41453285/article/details/95953571。
如果没有via首部的情况可以根据Date首部来判断请求是否命中,将相应中的date与实际时间比较,如果相应的date首部记录的时间比较早,就说明缓存被命中。其次可以考虑Age首部检测是否命中缓存,Age是表示的是请求的响应的时间,简单值观的应用就是Age的响应时间非常小的话就可以假定它是从缓存中取的数据,但是Age是一个估算值,并不是准确的时间。我在一个博客中看到有说使用Age值加上Date值对比当前时间,判断是否命中缓存这是对Date判断是否命中缓存的一种补充,这中方法我没有实际应用过不说那种更精确,毕竟这些都要根据项目的实际情况来选择和应用检测缓存命中率的策略。
1.6启动缓存(控制缓存首部Cache-Control与Expires)
Cache-Control请求/响应首部,缓存控制字段,是HTTP控制缓存的最高指令,要不要缓存怎么缓存都由它说了算。也就是说是否在缓存设备中缓存,和请求是否使用缓存都可以由它来控制。字段取值如下:
- no-store:禁止使用缓存(请求不读缓存,响应不缓存数据)。
- no-cache:使用缓存,但是在原始服务器进行缓存验证新鲜度之前,缓存不能使用缓存,服务器验证可以使用缓存后响应304状态码采用重定读取缓存,具体内容会在1.7的中详细介绍。HTTP/1.1中还提供了Pragma:on-cache来兼容HTTP/1.0应用程序,如果使用HTTP/1.1及以上版本的服务器需要兼容HTTP/1.0的客户端,使HTTP/1.0也能实现缓存在验证就需要尽可能的提供这个首部,但也不是所有服务兼容这个首部,但基本上主流服务都实现了。
- max-age=x(单位秒)请求资源响应后会被缓存x秒,如果再次请求不允许使用缓存的话在x秒内不会再向原始服务器请求数据,该属性值与http/1.0的expires首部实现的功能类似,但优先级比expires高。
- s-maxage=x(单位秒)代理服务器请求原始服务器后的x秒不再发起请求,可以理解为只对CDN缓存有效,毕竟现在实现HTTP缓存代理服务的通常都是CDN,实际上这个属性是指在一切HTTP公有缓存代理服务器上的缓存控制资源缓存x秒。
- public:客户端和代理服务器(CDN)都可以缓存
- private:只有客户端可以缓存
- must-revalidate:在缓存未过期的情况下直接使用缓存,如果缓存过期在向原始服务器验证新鲜度,这有利于提高系统性能,但也可能会导致使用陈旧的数据,根据具体业务需求使用,这个属性相对no-cache而言性能要好一些。详细可以参考这篇博客:https://segmentfault.com/a/1190000007317481。
Expires:响应头,代表资源过期时间,由服务器提供,GMT格式日期,是HTTP/1.0属性,与max-age(HTTP/1.1)共存情况下优先采用max-age。
//在相应保本中添加指定资源过期时间expires首部(nodejs) response.setHeader("Expires","Wed, 18 Mar 2020 07:20:25 GMT"); //指定资源缓存10分钟之后过期时间 let ShelfLife = new Date(Date.now()); ShelfLife.setMinutes(ShelfLife.getMinutes() + 10); response.setHeader("Expires",ShelfLife.toUTCString());
关于时间设定上在测试之前有个小疑问,就是在客户端是否会将本地系统时间转换成格林尼治时间,chrome浏览器会自动转换对照,所以并不需要在系统端将时间转成客户端所在时区的时间,毕竟从HTTP协议标准角度来说统一使用格林尼治时间就是为了规避时区冲突问题。
1.7缓存再验证
缓存再验证也在很多实际应用描述中被称为协商缓存,也就是在使用客户端取缓存数据之前向服务器发送一个验证报文首部,这个首部会将缓存中的数据报文信息发送到服务器,服务器再根据这些信息来判断缓存的数据是否过期,如果验证过期就直接从服务器响应最新的数据给客户端(响应状态码200),如果没有过期就只需要响应一个报文头部状态码为304,缓存代理服务器或客户端的代理会自动将资源请求自动重定向到缓存上,这样就实现了缓存在验证,也是通常说的协商缓存。
缓存再验证包含两种方案,一种是使用资源最后修改的日期来判断,一种是使用ETag标识来识别,一般静态文件资源使用日期验证就可以,但对于非静态资源(比如从文件中读取的一段文本数据)这时候就没办法使用日期的方式来判断了,这种情况下一般会采用数据的签名来识别,通过ETag从原始服务器响应数据时就带上MD5或者sha1等算法或者其他方法生成的标识作为ETag首部的属性值,当缓存向服务器发送再验证报文头时会自动带上这个标识,用于服务器作为新鲜度验证的依据。
值得注意的是,静态文件使用修改日期验证也是存在风险的,因为两个文件的修改日期是有可能一样的,毕竟文件修改日期之精确到秒,但如果采用数据签名验证就不会出现这类问题,关于MD5和sha1算法可以了解这篇博客:https://blog.csdn.net/llzhang_fly/article/details/79975323。在nodejs服务中比较常见的是使用crypto工具模块来生成MD5和sha1签名相关可查看廖雪峰大佬的个人博客:https://www.liaoxuefeng.com/wiki/1022910821149312/1023025778520640。(我后面的示例也是采用crypto工具模块来实现生成Etag标识,关于crypto的源码它不是js语言,而是使用C++扩展的nodejs模块,毕竟在这类算法上C++相对js来说更有优势,后续有时间我会使用js来模拟实现提供参考)
关于缓存在验证的HTTP首部:
If-Modified-since:Date(请求头)与Last-Modified:Date(相应头):这两个报文首部可能会比较陌生,因为一般情况下我们不会主动发送这两个报文首部,服务和浏览器会主动为我们添加这两个报文首部并赋值静态文件最后修改的时间,但是需要注意的是ajax同样会自动使用缓存,浏览器的缓存代理也会将对应资源的Last-Modified首部发送到原始服务,但是使用浏览器控制台查看请求报文是不可见的,而且有时候取用缓存不会并不会向服务器发送在验证报文头,F5刷新页面也会出现类是情况,这种情况都是出现在频繁刷新和频繁触发同一个ajax资源请求的情况下它们不会向原始服务发送在验证请求,但是稍微间隔一些时间后如果缓存还未过期就会向服务器发送再验证请求。值得注意的是ajax如果发送再验证请求使用缓存响应状态码304,如果不发送再验证请求就使用缓存状态码200,而F5刷新页面也同样会出现发送再验证报文或不发送的情况,且状态码有时为304有时为200,所以如果单纯从响应码的角度来说并不能判断是否发起来验证请求或不发送再验证请求。但是F5刷新HTML文件是100%会发起再验证请求的,所以有时候即使使用再验证也不能保证就可以确定出HTML文件以外的数据是最新的,这也是HTTP缓存有时会非常头疼的一个问题,为了解决这个问题除了再验证就好的方式就是采用资源hash值命名,而不是采用相同的名称来命名已修改的资源。(以上测试均在chrome浏览器下进行的)
If-None-Match:EtagString(请求头)与Etag:string(相应头):这两个报文首部Etag应该是比较熟悉的,也就是采用数据签名识别数据是否刷新的首部,同样配合cache-control或者expires首部来实现缓存再验证,浏览器对于Etag缓存再验证的表现与Last-Modified表现并不一致,如果对HTML资源采取了ETag签名再验证,每次刷新会对HTML资源再验证,但是HTML内部其他缓存资源就不会执行再验证操作,而是直接取缓存。HTML资源再验证响应码304(也有可能出现200状态码,但依然是从缓存取数据),其他缓存资源不验证(不向原始服务发送再验证请求)并且响应码为200,即便使用ajax请求表现也是如此。还有关于W/"v2.1"这类ETag标识表达的弱验证暂时没有太明白,如果有了解的朋友请留言赐教。(chrome浏览器测试)
(这里载添加一个说明,关于Etag签名HTML内部资源不执行再验证,是因为我测试的时候是手动写入的ETag值,响应的所有数据都使用了这个签名,导致只有html文件执行再验证,后面使用md5生成的数据签名会执行html内部数据的再验证,这个不正常的测试也让我明白了浏览器底层对于数据再验证居然是基于ETag,也就是可以理解当一个页面有两个数据的ETag签名一致,浏览器缓存代理就只会再验证第一个,后面的就不会再验就直接使用,无论这两个资源是否一致,甚至URL资源定位都会忽略)
上面多次提到缓存再验证首部会被自动添加,这只是服务和缓存代理实现的默认功能,但不是说缓存再验证就不需要额外的操作,恰恰相反缓存再验证是必须手动验证然后响应304重定向报文给缓存代理,告诉缓存代理可以使用缓存,并且会将缓存中的数据报文头更新为当前最新响应的报文头。
由于服务和代理自动默认行为导致资源不更新,但是也不用太当心,缓存再验证只是在频繁的刷新情况下才会出现不发送再验证报文,这个测试也仅仅只是在浏览器缓存中的表现,但问题又来了,CDN缓存会不会因为频繁使用而不发送再验证报文呢?毕竟CDN作为公共缓存代理会相比浏览器单个用户被使用的更频繁,这个问题我暂时不能测试。关于缓存不更新的问题可以使用ajax加载并手动使用If-Modified-Since或者If-None-Match发送一个与服务不匹配的参数(一般使用-1)实现强制再验证,在一些博客中又见到说还可以使用Cache-Control:no-store阻止使用缓存,测试发现并不能实现。重命名是可以肯定解决这类问题,因为重命名在HTTP协议看来是资源定位(URL)发生了改变,这个方式解决问题但也面临一些问题,如果只是简单的HTML页面还好修改,但如果采用单页面模块化和组件化的方式构建的HTML资源就等于要重新打包处理,这将是一个非常消耗时间的工作,要解决这个问题就会上升到前端的架构了,这方面问题我还在研究中,待这方面有比较成熟的了解以后再在其他博客中探究这个问题。
1 //测试ajax强制缓存再验证资源缓存 2 let imgButDom = document.getElementById("imgBut"); 3 imgButDom.onclick = function () { 4 let imageTagDom = document.getElementById("imageTag"); 5 let xmlHttp = new XMLHttpRequest(); 6 xmlHttp.responseType = "blob"; 7 //get请求方法 8 xmlHttp.open("GET","/image.jpg"); 9 // xmlHttp.setRequestHeader("Cache-Control","no-store");//不能实现 10 // xmlHttp.setRequestHeader("If-Modified-since","-1");//能实现强制再验证 11 xmlHttp.setRequestHeader("If-None-Match","-1");//能实现强制再验证 12 xmlHttp.send(null); 13 xmlHttp.onreadystatechange = function(){ 14 if(xmlHttp["readyState"] === 4){ 15 if(xmlHttp["status"] === 200 || xmlHttp["304"]){ 16 let blob = this.response; 17 console.log("获取到了图片资源"); 18 imageTagDom.src = window.URL.createObjectURL(blob); 19 console.log(this); 20 console.log(xmlHttp); 21 } 22 } 23 } 24 }
1.8缓存新鲜度限制
关于新鲜度限制就是在请求头中添加限制头部,指定请求是否使用缓存,但是这些请求头可能并不能确定可以实现,这需要浏览器的支持,比如前面提到使用Cache-Control:no-store添加到ajax请求头中并不能实现绕过缓存从原始服务器上直接获取数据,而是使用再验证的方式来保障资源的新鲜度更可靠,但是这里还是将这些请求首部和参数设置展示一下,毕竟属于HTTP标准的一部分,不能实现的原因是因为浏览器的缓存代理是否依照标准实现,这涉及到原因很多,但我想主要原因还是浏览器对性能的考虑。
不能实现的根本还是在于当服务对缓存行为做了限制,也就是响应首部中对相关行为做了指定性限制,请求首部就会失效,所以这时候就只能使用再验证的方式来实现,而当服务没有对资源缓存做出指定性限制时,请求限制就可以实现。
相关请求首部及参数:
- Cache-Control:max-stale:缓存可以随意提供过期文件。
- Cache-Control:max-stale=<s>:在s秒内文档不能过期。
- Cache-Control:min-fresh=<s>:至少在s秒内文档要保持新鲜。
- Cache-Control:max-age=<s>:缓存不能使用缓存超过s秒的文档。
- Cache-Control:no-cache:除非资源再验证,否则不能使用缓存。
- Pragma:no-cache:同上。
- Cache-Control:no-store:将缓存中的相关数据删除。
- Cache-Control:only-if-cached:只有当缓存中有副本存在时,客户端才会获取一份副本。
通过请求首部来控制缓存的情况很少,请求时我们更多希望做的事情还是怎么获取最新的数据,而不是作为缓存的控制方,这里还可能存在安全问题。
1.9用户行为与缓存
二、HTTP缓存策略
所谓强缓存和协商缓存在HTTP协议中并没有这样的概念,这是在应用缓存时的一种表达,强缓存就是指只要缓存中有符合资源定位相符的资源就用,不用考虑原始服务器中是否有对该资源进行更新。而协商缓存就是指可以使用缓存,但使用前必须与原始服务器进行资源的新鲜度再验证,保证获取的数据是最新的。协商缓存实际上就是在在强缓存的基础上,给Cache-Control添加“no-cache”,HTTP/1.0添加Pragma:on-cache来告诉缓存代理在使用缓存前先到原始服务器进行验证,具体机制已经在前面解析过了。
2.1强缓存策略:一般情况下都是用Cache-Control:max-age与兼容HTTP/1.0的Expires首部来实现,这里我将缓存时间设定位一个月。
1 let ShelfLife = new Date(Date.now()); 2 let ShelfLifeMonth = ShelfLife.getMonth(); 3 if(ShelfLifeMonth < 11){ 4 ShelfLife.setMonth(ShelfLifeMonth + 1); 5 }else{ 6 ShelfLife.setFullYear(ShelfLife.getFullYear() + 1); 7 ShelfLife.setMonth(0); 8 } 9 response.setHeader("Expires",ShelfLife.toUTCString()); 10 response.setHeader("Cache-Control","max-age=" + 2592000);
2.2协商缓存策略(If-Modified-Since:Date与Last-Modified:Date)
1 let pathName = url.parse(request.url).pathname; 2 let data = fs.readFileSync(serverConfig["page_path"] + pathName); 3 let fsStat = fs.statSync(serverConfig["page_path"] + pathName); 4 let ShelfLife = new Date(Date.now()); 5 let ShelfLifeMonth = ShelfLife.getMonth(); 6 if(ShelfLifeMonth < 11){ 7 ShelfLife.setMonth(ShelfLifeMonth + 1); 8 }else{ 9 ShelfLife.setFullYear(ShelfLife.getFullYear() + 1); 10 ShelfLife.setMonth(0); 11 } 12 response.setHeader("Expires",ShelfLife.toUTCString()); 13 response.setHeader("Pragma","no-cache"); 14 response.setHeader("Cache-Control","no-cache,max-age=" + 2592000); 15 if(request.headers["if-modified-since"] === fsStat.ctime.toUTCString()){ 16 response.writeHead(304); 17 response.end(); 18 }else{ 19 response.writeHead(200); 20 response.write(data); 21 response.end(); 22 }
2.3协商缓存策略(If-None-Match:Tag与ETag:Tag)
1 let http = require("http"); 2 let url = require("url"); 3 let fs = require("fs"); 4 let crypto = require("crypto"); 5 6 let serverConfig = require("./config.js");//解析配置文件模块(最后面我会附上全部代码) 7 8 let data = fs.readFileSync(serverConfig["page_path"] + pathName); 9 let fsStat = fs.statSync(serverConfig["page_path"] + pathName); 10 let md5 = crypto.createHash('md5'); 11 let flag = md5.update(data).digest("hex"); 12 let ShelfLife = new Date(Date.now()); 13 let ShelfLifeMonth = ShelfLife.getMonth(); 14 if(ShelfLifeMonth < 11){ 15 ShelfLife.setMonth(ShelfLifeMonth + 1); 16 }else{ 17 ShelfLife.setFullYear(ShelfLife.getFullYear() + 1); 18 ShelfLife.setMonth(0); 19 } 20 response.setHeader("Expires",ShelfLife.toUTCString()); 21 response.setHeader("Pragma","no-cache"); 22 response.setHeader("Cache-Control","no-cache,max-age=" + 2592000); 23 response.setHeader("ETag", flag); 24 if(request.headers["if-none-match"] === flag){ 25 response.writeHead(304); 26 response.end(); 27 }else{ 28 response.writeHead(200); 29 response.write(data); 30 response.end(); 31 }
三、nodejs实现http缓存策略模块(cache.js)
1 let crypto = require("crypto"); 2 3 //禁止缓存 4 function noCacheFun(response){ 5 response.setHeader("Cache-Control","no-Store"); 6 response.setHeader("Expires","-1"); 7 return true; 8 } 9 10 //强缓存-- 11 // --通过response会话对象给响应报文添加强制缓存的首部 12 // --缓存实际设置为一个月 13 // --scope可以用来指定缓存范围,默认取值public 14 // ----取值public:客户端和代理服务器都可以缓存 15 // ----取值private:只有客户端可以缓存 16 function compelCacheFun(response,scope){ 17 scope = scope | "public"; 18 cacheE(response); 19 response.setHeader("Cache-Control","max-age=2592000," + scope); 20 return true; 21 } 22 23 //协商缓存 24 // --参数:http请求对象request:用于获取请求报文中携带的if-none-match首部数据(该数据由服务响应给客户端的ETag提供给代理缓存) 25 // -- http会话响应对象response:用于设置缓存首部属性及再验证响应 26 // -- data:http客户端需要的的数据资源,用于生成数据签名flag 27 // -- scope:指定缓存范围(参考强缓存参数说明) 28 function consultCacheFun(request,response,data,scope){ 29 scope = scope | "public"; 30 let md5 = crypto.createHash("md5"); 31 let flag = md5.update(data).digest("hex"); 32 cacheE(response); 33 response.setHeader("ETag",flag); 34 response.setHeader("Cache-Control","no-cache,max-age=2592000," + scope); 35 if(request.headers["if-none-match"] === flag){ 36 response.writeHead(304); 37 response.end(); 38 return false; 39 } 40 return true; 41 } 42 43 function cacheE(response,scope){ 44 let shelfLife = new Date(Date.now()); 45 let shelfLifeMonth = shelfLife.getMonth(); 46 if(shelfLifeMonth < 11){ 47 shelfLife.setMonth(shelfLifeMonth + 1); 48 }else{ 49 shelfLife.setFullYear(shelfLife.getFullYear() + 1); 50 shelfLife.setMonth(0); 51 } 52 response.setHeader("Expires",shelfLife.toUTCString()); 53 } 54 55 module.exports = { 56 "no-cache": noCacheFun, 57 "compel-cache":compelCacheFun, 58 "consult-cache":consultCacheFun, 59 }
测试代码(服务入口文件):
1 let http = require("http"); 2 let url = require("url"); 3 let fs = require("fs"); 4 let cache = require("./cache.js"); 5 6 let serverConfig = require("./config.js");//解析配置文件模块 7 8 http.createServer(function(request,response){ 9 let pathName = url.parse(request.url).pathname; 10 let params = url.parse(request.url,true).query; 11 let isStatic = isStaticsRequest(pathName); 12 if(isStatic){ 13 try{ 14 let data = fs.readFileSync(serverConfig["page_path"] + pathName); 15 16 if(cache["consult-cache"](request,response,data)){ 17 response.writeHead(200); 18 response.write(data); 19 response.end(); 20 } 21 }catch (e) { 22 response.writeHead(404); 23 response.write("请求资源出错!") 24 response.end(); 25 throw e; 26 } 27 }else{ 28 response.writeHead(200); 29 response.write(JSON.stringify(params)); 30 response.end(); 31 } 32 }).listen(12306); 33 34 35 //判断是否请求静态资源的工具方法 36 function isStaticsRequest(pathName){ 37 for(let i = 0; i < serverConfig["staticFileType"].length; i++){ 38 let temp = serverConfig["staticFileType"][i]; 39 if(pathName.indexOf(temp) !== -1 && pathName.indexOf(temp) === pathName.length - temp.length){ 40 return true; 41 } 42 } 43 return false; 44 }
1 port=12306 2 static_file_type=.html|.js|.css|.json|.jpg|.gif|.ico 3 page_path=page
1 //自定义读取配置文件模块 2 let fs=require("fs"); 3 4 function analysisConfig(configFile){ 5 let obj = {}; 6 let arr = configFile.toString().split("\r\n"); 7 for(let i = 0; i < arr.length; i++){ 8 let item = arr[i].split("="); 9 if(item[0] === "static_file_type"){ 10 obj["staticFileType"] = item[1].split("|"); 11 }else{ 12 obj[item[0]] = item[1]; 13 } 14 } 15 return obj; 16 }
1 <!doctype html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" 6 content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> 7 <meta http-equiv="X-UA-Compatible" content="ie=edge"> 8 <title>Document</title> 9 <link rel="icon" href="data:image/ico;base64,aWNv"> 10 </head> 11 <body> 12 <h3>测试get与post请求</h3> 13 <p> 14 <span></span>账号:<input id="userId" type="text"> 15 <span>密码:</span><input id="userPassword" type="password"> 16 <input type="submit" value="登入" onclick="login()"> 17 </p> 18 <img id="imageTag" src="image1.jpg" alt=""> 19 <button id="imgBut">加载图片</button> 20 <script> 21 function login(){ 22 let userIdValue = document.getElementById("userId").value; 23 let userPasswordValue = document.getElementById("userPassword").value; 24 let params = "userId=" + userIdValue + "&userPassword=" + userPasswordValue; 25 let paramsJson =JSON.stringify({userId:userIdValue,userPassword:userPasswordValue}); 26 let xmlHttp = new XMLHttpRequest(); 27 //get请求方法 28 // xmlHttp.open("GET","/login?" + params,true); 29 //post请求方法 30 xmlHttp.open("POST","/login",true); 31 xmlHttp.setRequestHeader('Content-type','application/x-www-form-urlencoded'); 32 // xmlHttp.send(null); 33 xmlHttp.send(paramsJson); 34 35 // xmlHttp.send(null); 36 xmlHttp.onreadystatechange = function(){ 37 if(xmlHttp["readyState"] === 4){ 38 if(xmlHttp["status"] === 200){ 39 console.log(xmlHttp["responseText"]); 40 } 41 } 42 } 43 } 44 45 //测试ajax强制缓存再验证资源缓存 46 let imgButDom = document.getElementById("imgBut"); 47 imgButDom.onclick = function () { 48 let imageTagDom = document.getElementById("imageTag"); 49 let xmlHttp = new XMLHttpRequest(); 50 xmlHttp.responseType = "blob"; 51 //get请求方法 52 xmlHttp.open("GET","/image.jpg"); 53 // xmlHttp.setRequestHeader("Cache-Control","no-store");//不能实现 54 // xmlHttp.setRequestHeader("If-Modified-since","-1");//能实现强制再验证 55 xmlHttp.setRequestHeader("If-None-Match","-1");//能实现强制再验证 56 xmlHttp.send(null); 57 xmlHttp.onreadystatechange = function(){ 58 if(xmlHttp["readyState"] === 4){ 59 if(xmlHttp["status"] === 200 || xmlHttp["304"]){ 60 let blob = this.response; 61 console.log("获取到了图片资源"); 62 imageTagDom.src = window.URL.createObjectURL(blob); 63 console.log(this); 64 console.log(xmlHttp); 65 } 66 } 67 } 68 } 69 70 </script> 71 </body> 72 </html>
//path结构 --工作区间 page --image.jpg --image1.jpg --login.html cache.js config.js index.js server.config