谈论HTTP缓存的那些事情
前言
作为优化Web应用性能的重要手段,HTTP缓存机制应该是知识体系的基础环节,也是成为从事Web开发的学生前端架构的必备技能。
缓存的作用
我们使用缓存的原因是缓存可以给我们的Web项目带来以下好处,从而提高性能和用户体验。
加快浏览器加载网页的速度;减少冗余数据传输,节省网络流量和带宽;减轻服务器负担,大幅提升网站性能。因为从本地缓存读取静态资源在一定程度上加快了浏览器的网页加载,确实减少了数据传输,在提升网站性能方面,可能一两个用户的访问对减轻服务器负担没有明显的效果,但是如果网站处于高并发的情况下,使用缓存对降低服务器压力和整个网站的性能会有质的变化。
缓存规则介绍
为了便于理解,我们认为浏览器有一个缓存数据库,用于存储缓存信息(实际上,静态资源缓存在内存和磁盘中)。当浏览器第一次请求数据时,缓存数据库中没有对应的缓存数据,所以需要请求服务器,服务器会返回缓存规则和数据,浏览器会存储在缓存数据库中。
当在浏览器的地址栏中输入地址时,请求的index.html不会被缓存,但是在index.html请求的其他资源将遵循缓存策略。HTTP缓存有各种规则,主要根据是否需要向服务器发送请求分为两类,即强制缓存和协商缓存。
强制缓存
1.强制缓存过程
强制缓存是指第一次访问服务器获取数据后,在有效时间内不会再次请求服务器,而是直接使用缓存的数据。强制缓存的过程如下。
2.强制缓存判断到期时间
那么如何确定缓存是否过期呢?其实是第一次访问时根据服务器的响应头来实现的,HTTP 1.0和HTTP 1.1不一样。
在http版本中,服务器使用的响应头字段是Expires,其值是未来的绝对时间(时间戳)。当浏览器请求时,当前时间超过Expires设置的时间,这意味着缓存无效,因此需要再次向服务器发送请求,否则将直接从缓存数据库中获取数据。
在http版本中,服务器使用的响应头字段是Cache-Control,它有多个不同含义的值。
Private:客户端可以缓存;Public:客户端和代理服务器都可以缓存(对于前端来说,可以认为和private有同样的效果);Max-age=xxx:缓存内容将在xxx秒后过期(相对时间,以秒为单位);无缓存:您需要使用协商缓存(稍后描述)来验证数据是否过期;无存储:不会缓存所有内容,也不会触发强制缓存和协商缓存。Max-age=xxx是Cache-Control最常用的值,缓存本身是为了数据传输的优化和性能而存在的,所以几乎不使用无存储。
注意:在HTTP版本中,Expires字段的绝对时间是从服务器获取的。由于请求需要时间,浏览器的请求时间和服务器收到请求时获得的时间存在误差,也导致缓存命中的错误。在HTTP 1.0版本中,由于Cache-Control max-age=xxx的值是以秒为单位的相对时间,浏览器收到资源后倒计时开始,避免了HTTP 1.0中缓存命中错误的缺陷。为了兼容较低版本的HTTP协议,在正常开发中会同时使用两个响应头,HTTP 1.1版本的实现优先级高于HTTP 1.0。
3.通过网络检查强制缓存
通过Chrome浏览器的开发者工具,我们可以打开NetWork查看强制缓存的相关信息。
以上是百度网站Logo图片的回应。我们可以清楚地看到,它兼容HTTP 1.0和HTTP 1.1版本,并且已经在强制缓存中存储了10年。
让我们看看通过缓存和网络中的其他资源获取的数据之间的差异。
实际上,缓存存储在两个位置:内存和磁盘,这是由当前浏览器自己的策略决定的。它是随机的,从内存缓存中检索的数据将被显示(从内存缓存中),从磁盘缓存中检索的数据将被显示(从磁盘缓存中)。
4.NodeJS服务器实现强制缓存
//strong制缓存常量http=require(' http ');const URL=require(' URL ');const path=require(' path ');const mime=require(' mime ');const fs=require(' fs ');const server=http . CreateServer((req,RES)={ let { pathname }=URL . parse(req . URL,true);路径名=路径名!=='/' ?pathname : '/index . html ';//获取读取文件的绝对路径让p=path . join(_ dirname,pathname);//检查路径是否合法。fs.access(p,err={//如果路径非法,如果(err)返回res.end('Not Found '),直接中断连接;//设置强制缓存res.setheader ('expires ',新日期(date . now())30000。togtstring());res.setHeader('缓存控制','最大年龄=30 ');//设置文件类型并响应浏览器res.setheader ('content-type ',` $ { mime . gettype(p)};charset=utf8 `);fs.createReadStream(p)。管道(RES);});});server.listen(3000,()={ console . log(' server start 3000 ');});上述mime模块的getType方法可以成功返回传入路径下文件对应的文件类型,如text/html和application/javascript,是第三方模块,使用前需要安装。
Npm安装mime协商缓存
1.协商缓存过程
协商缓存也称为比较缓存。设置协商缓存后,第一次访问服务器获取数据时,服务器将数据和缓存标识符返回给浏览器,客户端将数据和标识符存储在缓存数据库中。下次请求时,会先取出缓存标识,发送给服务器查询。当服务器数据发生变化时,会更新标识符,所以服务器得到浏览器发送的标识符进行比较,相同的代表数据没有变化。响应于浏览器关于数据没有改变的通知,浏览器将去缓存获取数据。如果标识符不同,则意味着服务器已经更改了数据,因此新的数据和标识符将返回给浏览器,浏览器会将新的数据和标识符存储在缓存中。协商缓存的过程如下。
协商缓存和强制缓存的区别在于,协商缓存每次请求都需要和服务器通信,命中缓存服务器返回的状态码是304而不是200。
2.协商缓存判断标识符
强制缓存控制是否通过过期时间访问服务器,而协商缓存每次都与服务器交互,比较缓存标识。同样,协商缓存的实现在HTTP 1.0和HTTP 1.1之间也是不同的。
在http版本中,服务器通过Last-Modified响应头设置缓存标识符,通常以请求数据的最后修改时间(绝对时间)为值,而浏览器将接收到的数据和标识符存储在缓存中,并在再次请求时自动发送If-Modified-after请求头,该值为之前返回的最后修改时间(标识符)。服务器取出“如果-修改-自”的值,并将其与数据的上次修改时间进行比较。如果最后修改时间大于If-Modified-after的值,则表明它已经被修改,然后通过最后修改响应头返回新的最后修改时间和新数据,否则它没有被修改,并且通过返回状态码304通知浏览器命中高速缓存。
在http版本中,服务器通过Etag响应头设置缓存标识符(唯一标识符,就像指纹一样,生成规则由服务器决定),浏览器收到后将数据和唯一标识符存储到缓存中。在下一个请求中,唯一标识符通过If-None-Match请求头被带到服务器,服务器取出唯一标识符并将其与前一个标识符进行比较。如果相同,它将返回新的标识符和数据,但是如果相同,它将返回状态代码300。
HTTP协商缓存策略的流程图如下:
注意:使用协商缓存时,HTTP 1.0版本仍然不可靠。假设某个文件在添加字符后被删除,则该文件相当于未更改,但最后一次修改时间被更改,将被视为修改。它应该命中缓存,但是服务器重新发送数据。因此,HTTP 1.1中使用的唯一Etag标识符是根据文件内容或摘要生成的,这确保了只要文件内容保持不变,它就一定会命中缓存。为了兼容较低版本的HTTP协议,开发中会同时使用两种响应头,HTTP 1.1版本的实现优先级高于HTTP 1.0。
3.通过网络检查协商缓存
我们还使用Chrome浏览器的开发者工具打开NetWork查看协商缓存的相关信息。
再次请求服务器的请求头信息:
命中协商缓存的响应头信息:
让我们看看通过协商缓存获取的数据和网络中第一次加载的数据之间的差异。
第一个要求:
缓存后请求:
通过两图对比,我们可以发现协商缓存生效时的状态码为304,消息大小和请求时间大大减少,因为服务器只是在标识比较后才返回头部分,并通过状态码通知浏览器使用缓存,因此不再需要将消息正文部分一起返回给浏览器。
4.NodeJS服务器实现协商缓存
//协商缓存常量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 : '/index . html ';//获取读取文件的绝对路径让p=path . join(_ dirname,pathname);//检查路径是否合法fs.stat(p,(err,statObj)={//如果路径非法,如果(err)返回res.end('Not Found ')直接中断连接;let MD5=crypto . create hash(' MD5 ');//创建加密转换流let RS=fs . createreadstream(p);//创建可读流//读取文件内容并加密rs.on ('data ',data=MD5 . update(data));rs.on('end ',()={ let ctime=statobj . ctime . toggmtstring();//获取文件let标志=md5.digest('hex ')的最后修改时间;//获取加密的唯一id//获取协商缓存let的请求头ifmodify自=req . headers[' if-modified-自'];让if none match=req . headers[' if-none-match '];if(ifmodifiedfather===ctime | | ifnone match===标志){ res.statusCode=304RES . end();} else {//设置协商缓存res.setheader('上次修改',ctime);res.setHeader('Etag ',标志);//设置文件类型并响应浏览器res.setheader ('content-type ',` $ { mime . gettype(p)};charset=utf8 `);RS . pipe(RES);} });});});server.listen(3000,()={ console . log(' server start 3000 ');});在上面的代码中,通过可读流读取文件的内容,并以md5加密的结果作为唯一标识符,以保证只要文件的内容保持不变,就会命中缓存,与HTTP 1.0和HTTP 1.1版本兼容。如果满足一个,它将直接返回304以通知浏览器它命中高速缓存。
注意:事实上,读取文件内容并加密是不可取的。如果您正在读取一个大文件,读取文件内容并用md5加密将花费大量时间。所以在开发的时候要根据业务的实际情况选择一种保证服务器性能的方式,比如根据文件的摘要。
摘要
为了使缓存策略更加健壮和灵活,将同时使用HTTP 1.0和HTTP 1.1的缓存策略,甚至会同时使用强制缓存和协商缓存。对于强制缓存,服务器将通知浏览器缓存时间,在缓存时间内,下一个请求将直接使用缓存,超过有效时间将执行协商缓存策略。缓存信息中的Etag和上次修改通过请求头If-None-Match和If-Modified-before发送到服务器,服务器同时验证并设置新的强制缓存。当验证通过并返回304状态码时,浏览器直接使用缓存,如果协商缓存也未命中,服务器重置协商缓存的标识符。
以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。
版权声明:谈论HTTP缓存的那些事情是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。