一、引言
前两天面试一个问题,“对HTTP
304
状态码有了解吗”,确实经常看到,却对其中原理不是很了解, 因此特意翻阅相关资料,整理如下。由于目前对HTTP没有太深入的了解,此文章只从较浅的层面分析http缓存问题
参考以下文章:彻底理解浏览器的缓存机制(http缓存机制)HTTP 强制缓存和协商缓存
二、HTTP缓存机制
浏览器只是遵循http协议的一个载体,与其说是浏览器的缓存机制,不如说是http的缓存机制
HTTP缓存主要分成两类,分别是 强制缓存
和 协商缓存
1. 强制缓存
强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程
返回的状态码一定是200,哪怕是从浏览器缓存中读取的数据,主要以
Expires
和Cache-Control
响应头控制。
存在Expires时
Expires是HTTP/1.0控制网页缓存的字段,其值为服务器返回文件缓存的到期时间,即再次发送请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。
Expires
控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,如果客户端与服务端的时间由于某些原因(时区不同;客户端和服务端有一方的时间不准确)发生误差,那么强制缓存直接失效。因此HTTP升级后主要以Cache-Control为主
存在Cache-Control
Cache-Control主要取值有以下几个
① public
http请求返回的过程中,返回的数据可以在路径中的每个节点(浏览器,代理服务器如nginx,服务器)被缓存,一般不会单独存在,比如服务端响应头可能是Cache-Control: public,max-age=200,则代表文件被服务端和客户端缓存,有效时间为200秒,在200秒内访问返回缓存,否则发送http请求
② private
所有内容只有客户端可以缓存,Cache-Control的默认取值,一般也是搭配max-age,即在多少秒内直接读取缓存,返回200状态码,否则去服务端请求
③ no-cache
客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定,具体请看最下面的协商缓存
强制客户端直接向服务器发送请求,也就是说每次请求都必须向服务器发送。服务器接收到请求,然后判断资源是否变更,是则返回新内容,否则返回304,未变更。这个很容易让人产生误解,使人误以为是响应不被缓存。实际上Cache-Control: no-cache是会被缓存的,只不过每次在向客户端(浏览器)提供响应数据时,缓存都要向服务器评估缓存响应的有效性。
④ no-store
所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存,是真正意义上的不缓存
⑤ max-age=xxx
缓存内容将在xxx秒后失效,重新发送请求
如果设置max-age=0,不会直接发新的请求,会先走协商缓存
2. 协商缓存
协商缓存说的简单点就是强制缓存失效,浏览器发送请求的时候携带缓存标识,由服务端决定是否使用本地缓存的过程
主要有两个HTTP头决定,分别是
LastModified
和ETag
其中ETag优先级高于LastModified,二者都存在则以ETag为准。
使用ETag时:
第一次发送请求时,服务端会为当前资源生成唯一标识符,浏览器缓存响应结果,第二次请求时会携带一个 If-None-Match
请求头,其值就是本地缓存的ETag的值,如果两个值相同,则证明文件没有修改,返回304状态码,否则返回200状态码
使用LastModified
第一次请求一个文件时,服务端会响应一个LastModified响应头,浏览器端缓存这个文件,第二次请求时会携带一个If-Modified-Since请求头,其值为缓存文件的Last-Modified,服务端根据这个请求头进行比较,如果文件未更新,则返回304状态码,否则返回200状态码
二者都存在
如果响应头中既存在ETag也存在LastModified,则先判断ETag,如果ETag不相同,则返回200,如果ETag相同,在比较LastModified,如果也相同就返回304,否则返回200状态码
三、实例
1. 协商缓存实例
通过nodejs+express简单搭建一个html页面,由于 ETag
优先级高于 LastModified
,只测试两个例子,第一个保持 LastModified
不变,只更改 ETag
响应头,第二个保持 ETag
不变,更改 LastModified
控制ETag
// 通过res渲染一个html页面,设置一个响应头ETag,值固定
common.get("/test",async (req,res) => {
res.setHeader("ETag","adfasfksldj")
res.render('index');
})
如果只设置ETag,且保持不变,则第一次请求为200,以后每次请求都是304第一次请求
![image.png](https://i-blog.csdnimg.cn/blog_migrate/f3f4206b565c142b7da321935bcedbe4.png)
![image.png](https://i-blog.csdnimg.cn/blog_migrate/545efd53dba51b52380ca5ecc78baf81.png)
结论:响应头中没有Cache-Control,走协商缓存,只存在ETag时,服务端只需要判断请求头中的
If-None-Match
和服务器生成的ETag
是否相同, 如果相同就返回304,不同则返回200
控制LastModified
common.get("/test",async (req,res) => {
res.setHeader('Last-Modified',new Date().toGMTString());
res.setHeader("ETag","adfasfksldj")
res.render('index');
})
第一次访问
![image.png](https://i-blog.csdnimg.cn/blog_migrate/9248b5103d9881c96c539e4d3e3c5250.png)
状态码200 第二次访问
![image.png](https://i-blog.csdnimg.cn/blog_migrate/e46065ffcdfa193bdafbb012a0a1b0e1.png)
由于
Last-Modified
改变,ETag没改变,更验证了结论
如果
ETag
相同, 服务端会验证Last-Modified
,如果Last-Modified
和请求头中的If-Mofidied-Since
不同,也不会采用缓存,而是返回200状态码