协商缓存和强缓存是HTTP协议中的两种不同的缓存机制,用于优化网络性能和减少重复请求。它们在客户端和服务器之间进行通信,以确定是否使用缓存副本或请求最新资源。
1. 强缓存(Expires和Cache-Control):
强制缓存的思想是,在浏览器内置数据库中缓存每次请求中 “可以被缓存” (受到一些关键字的管控)的静态资源如 image, css, js 文件, 当第二次请求被缓存过的资源时候,会通过校验两个字段 Expires 和 Cache-Control 的max-age字段(注意,Expires 是 http1.0 的产物, Cache-Control 则是 http1.1 的产物),如果两者同时存在, 或者只存在其中之一, 都可以触发强制缓存。
- Expires:表示响应过期的日期和时间。通过设置Expires响应头,服务器告诉浏览器资源的过期日期和时间。当浏览器发起请求时,如果本地缓存未过期,浏览器将直接使用缓存副本,而无需再次请求服务器。
- Cache-Control:表示指定请求和响应遵循的缓存机制。通过设置Cache-Control响应头,服务器可以提供更精确的缓存控制。常见的Cache-Control指令包括max-age、public、private、no-cache和no-store等。例如,设置max-age=3600表示资源在3600秒内有效,浏览器可以直接使用缓存。
当满足字段约束的情况下, 浏览器就不会向服务器发送请求而是直接从服务器返回数据, 同时其状态码为 200;当不满足字段约束的情况下, 浏览器则会向服务器正常发送请求。
强制缓存主要取决于两个字段 Expires 和 Cache-Control 中的 max-age 字段, 在两个响应头都存在的情况下。如上图强缓存部分所示。
2. 协商缓存(Last-Modified和ETag):
协商缓存主要由 ETag 和 Last-Modified 两个字段来实现
- Last-Modified:通常是文件最后更新的日期时间戳。服务器在响应头中返回资源的最后修改时间(GMT格式)。当浏览器再次请求资源时,通过If-Modified-Since请求头将上次的最后修改时间发送给服务器。如果资源的最后修改时间与服务器上的相同,服务器返回304 Not Modified响应,告诉浏览器可以使用缓存副本。
- ETag:ETag 是一个用于映射 web 资源的映射 token,这个 token 应该满足唯一对应到一 个web服务器上的静态资源(具体实现通常是提取文件相关信息进行hash和base64编码等操作),也是服务器在响应头中返回资源的唯一标识符(通常是哈希值)。当浏览器再次请求资源时,通过If-None-Match请求头将上次的ETag发送给服务器。如果资源的ETag与服务器上的相同,服务器返回304 Not Modified响应,告诉浏览器可以使用缓存副本。
与上述两个字段配对的分别是 If-None-Match 和 If-Modified-Since 这两个字段:
浏览器首次向服务器请求数据 A, 服务器正常返回数据,同时在响应头中放入 ETag 和 Last-Modified 两个新字段。
当浏览器第二次向服务器请求数据 A 时, 浏览器会自动地在请求头附上 If-None-Match 和 If-Modified-Since 两个字段(分别对应的是 ETag 和 Last-Modified 的值,两两相等), 然后由服务器端进行校验,校验通过的话(表明数据有效),服务器会直接返回状态码 304 ,且不携带响应体的报文段, 这相当于告诉浏览器:当前缓存有效, 可以直接使用! 校验失败则会和首次请求一样, 返回状态码为200且携带数据响应体的报文段, 同时这个响应头会带上新的ETag 和Last-Modified, 为下一次协商缓存做好铺垫 。
需要注意的是, 在不用框架的情况下, 协商缓存需要由后端开发人员手动实现,因此 ETag 和 Last-Modified 两个字段的优先级取决于开发者, 但是 Last-Modified 这个字段可以记录的时间戳精确度是有一定限制的,如果连续多次数据更新在精确度范围外, 会产生精确度丢失, 因此通常会让ETag 的优先级高于 Last-Modified 字段(类似于Cache-control中max-age一样, 属于是后续改进协议的一个新字段, 因此优先级一般会高点)
三、强缓存协商缓存并存的情况
默认情况下, 浏览器会优先考量强制缓存的情况, 当强制缓存生效的情况下, 请求并不会到达服务器, 因此也就不会触发协商缓存。 当强制缓存失效的时候, 浏览器便会将请求传递到服务器, 于是服务器又会开始校验 If-Modified-Since 和 If-None-Match 两个字段,重复上述协商缓存的一个执行流程。
在实际应用中,浏览器首先检查强缓存信息(Expires和Cache-Control),如果缓存仍然有效,则直接使用缓存。如果缓存已过期,浏览器发送带有协商缓存信息(Last-Modified和ETag)的请求到服务器进行验证。如果服务器返回304 Not Modified响应,则浏览器使用缓存;否则,服务器返回新的资源内容。综合使用强缓存和协商缓存可以减少对服务器的请求次数,提高网站的加载速度和性能。
乍一看,两者并存的情况, 有点像是两个协议的简单叠加,此时的协商缓存更像是强制缓存的兜底策略, 很可能协商缓存很长一段时间都不会生效(强制缓存过期时间设置过长的情况下), 因为强制缓存的优先级是要高于协商缓存的。 当然这并不是我们想看到的, 比方说当后端数据确实变更了, 而此时的浏览器由于使用了强制缓存,则会出现数据不一致的情况, 因此在这里引入了请求头中的两个字段 no-cache, 当使用了 no-cache 字段的时候, 浏览器将不再使用强制缓存, 而是直接去请求服务器, 这个时候就会用到协商缓存了(顺带一提的是, 还有一个 no-store 字段, 用了这个字段浏览器则不会在使用缓存的数据也不缓存数据,即强制缓存和协商缓存都失效了)
四、缓存机制之间的一些区别
-
强制缓存在缓存有效的情况下不会去请求服务器, 其数据来源则是浏览器缓存的本地磁盘。而协商缓存会向服务器请求,但是在协商缓存成功的情况下, 服务器只会返回一个不带响应体的报文,结合开头的背景来说 强制缓存选择“减少过桥次数”的策略, 而协商缓存则是采用 ‘减少过桥人数’的策略
-
强制缓存在浏览器强制刷新的情况下不会生效, 而协商缓存则不受影响。(调试代码测试时候,要注意)
-
强制缓存返回的报文状态码为 200, 协商缓存返回的报文状态码为 304 (前端使用fetch请求的情况, 协商缓存的 状态码304 会转成 200)
-
强制缓存发生在浏览器端, 协商缓存发生在服务器端
四、总结
- 强制缓存存在一个瓶颈, 当浏览器用户强刷新时,浏览器会直接跳过强制缓存,这点不注意很容易会被忽视掉。
- 强制缓存不适合 SPA 应用的入口文件, 因为重新部署后, 用户如果没有强制刷新, 则无法在第一时间内看到新的网页内容。
- 作为一个前端开发者可以通过设置请求头中的 no-cache 和 no-store 字段选择使用协商缓存或者不使用缓存!!!