由304引发的命案

一、引言

前两天面试一个问题,”对HTTP 304 状态码有了解吗”,确实经常看到,却对其中原理不是很了解, 因此特意翻阅相关资料,整理如下。由于目前对HTTP没有太深入的了解,此文章只从较浅的层面分析http缓存问题
参考以下文章:
彻底理解浏览器的缓存机制(http缓存机制)
HTTP 强制缓存和协商缓存

二、HTTP缓存机制

浏览器只是遵循http协议的一个载体,与其说是浏览器的缓存机制,不如说是http的缓存机制

HTTP缓存主要分成两类,分别是 强制缓存协商缓存

1. 强制缓存

强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程

返回的状态码一定是200,哪怕是从浏览器缓存中读取的数据,主要以 ExpiresCache-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头决定,分别是 LastModifiedETag 其中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

1
2
3
4
5
// 通过res渲染一个html页面,设置一个响应头ETag,值固定
common.get("/test",async (req,res) => {
res.setHeader("ETag","adfasfksldj")
res.render('index');
})

如果只设置ETag,且保持不变,则第一次请求为200,以后每次请求都是304
第一次请求
9d49da0e69e747548ec3a270a735967a
第二次请求

image.png

结论:响应头中没有Cache-Control,走协商缓存,只存在ETag时,服务端只需要判断请求头中的 If-None-Match 和服务器生成的 ETag 是否相同, 如果相同就返回304,不同则返回200

控制LastModified

1
2
3
4
5
common.get("/test",async (req,res) => {
res.setHeader('Last-Modified',new Date().toGMTString());
res.setHeader("ETag","adfasfksldj")
res.render('index');
})

第一次访问
17e6c5a6e78c484a93cf740ae3d7c401
状态码200
第二次访问

image.png
由于 Last-Modified 改变,ETag没改变,更验证了结论

如果 ETag 相同, 服务端会验证 Last-Modified ,如果 Last-Modified 和请求头中的 If-Mofidied-Since 不同,也不会采用缓存,而是返回200状态码