深入理解 HTTP 缓存机制

作者: 刘星
日期: 2021年4月26日

前言

开始前先来简单看看缓存是什么,缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。

我们使用 HTTP 缓存,通过复用缓存资源,减少了客户端等待时间和网络流量,同时也能缓解服务器端的压力。可以显著的提升我们网站和应用的性能。虽然 HTTP 缓存不是必须的,但重用缓存的资源通常是必要的,HTTP 缓存是一个 web 性能优化的重要手段。

HTTP 缓存的类型

通常 HTTP 缓存策略分为两种:强缓存和协商缓存。从字面意思我们可以很直观的看到它们的差别。强缓存即强制直接使用缓存。协商缓存就得和服务器协商确认下这个缓存能不能用。

强缓存

强缓存不会向服务器发送请求,直接从缓存中读取资源,在 chrome 控制台的 network 选项中可以看到该请求返回 200 的状态码,并且size显示from disk cachefrom memory cache

协商缓存

协商缓存会先向服务器发送一个请求,服务器会根据这个请求的 request header 的一些参数来判断是否命中协商缓存,如果命中,则返回 304 状态码并带上新的 response header 通知浏览器从缓存中读取资源。

HTTP 缓存控制

在 HTTP 中,我们可以通过设置响应头以及请求头来控制缓存策略。

强缓存可以通过设置ExpiresCache-Control 两种响应头实现。如果同时存在,Cache-Control优先级高于Expires

Expires

Expires 响应头,它是 HTTP/1.0 的产物。代表该资源的过期时间,其值为一个绝对时间。它告诉浏览器在过期时间之前可以直接从浏览器缓存中存取数据。由于是个绝对时间,客户端与服务端的时间时差或误差等因素可能造成客户端与服务端的时间不一致,将导致缓存命中的误差。如果在Cache-Control响应头设置了 max-age 或者 s-max-age 指令,那么 Expires 会被忽略。

Expires: Wed, 21 Oct 2015 07:28:00 GMT

Cache-Control

Cache-Control 出现于 HTTP/1.1。可以通过指定多个指令来实现缓存机制。主要用表示资源缓存的最大有效时间。即在该时间端内,客户端不需要向服务器发送请求。优先级高于 Expires。其过期时间指令的值是相对时间,它解决了绝对时间的带来的问题。

Cache-Control: max-age=315360000

Cache-Control 有很多属性,不同的属性代表的意义也不同。

可缓存性

  • public 表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存。
  • private 表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)
  • no-cache 不使用强缓存,需要与服务器验协商缓存验证。
  • no-store 缓存不应存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存。

过期

  • max-age=<seconds> 缓存存储的最大周期,超过这个周期被认为过期。
  • s-maxage=<seconds> 设置共享缓存。会覆盖max-ageexpires,私有缓存会忽略它
  • max-stale[=<seconds>] 客户端愿意接收一个已经过期的资源,可以设置一个可选的秒数,表示响应不能已经过时超过该给定的时间。
  • min-fresh=<seconds> 客户端希望在指定的时间内获取最新的响应

重新验证和重新加载

  • must-revalidate 如页面过期,则去服务器进行获取。
  • proxy-revalidatemust-revalidate 作用相同,但是用于共享缓存。

其他

  • only-if-cached 不进行网络请求,完全只使用缓存。
  • no-transform 不得对资源进行转换和转变。例如,不得对图像格式进行转换。

协商缓存可以通过 Last-Modified/If-Modified-SinceETag/If-None-Match这两对 Header 来控制。

Last-Modified、If-Modified-Since

Last-ModifiedIf-Modified-Since 的值都是 GMT 格式的时间字符串,代表的是文件的最后修改时间。

  1. 在服务器在响应请求时,会通过Last-Modified告诉浏览器资源的最后修改时间。

  2. 浏览器再次请求服务器的时候,请求头会包含Last-Modified字段,后面跟着在缓存中获得的最后修改时间。

  3. 服务端收到此请求头发现有if-Modified-Since,则与被请求资源的最后修改时间进行对比,如果一致则返回 304 和响应报文头,浏览器只需要从缓存中获取信息即可。如果已经修改,那么开始传输响应一个整体,服务器返回:200 OK

但是在服务器上经常会出现这种情况,一个资源被修改了,但其实际内容根本没发生改变,会因为Last-Modified时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)。为了解决这个问题,HTTP/1.1 推出了Etag。Etag 优先级高与Last-Modified

Etag、If-None-Match

Etag都是服务器为每份资源生成的唯一标识,就像一个指纹,资源变化都会导致 ETag 变化,跟最后修改时间没有关系,ETag可以保证每一个资源是唯一的。

在浏览器发起请求,浏览器的请求报文头会包含 If-None-Match 字段,其值为上次返回的Etag发送给服务器,服务器接收到次报文后发现 If-None-Match 则与被请求资源的唯一标识进行对比。如果相同说明资源没有修改,则响应返 304,浏览器直接从缓存中获取数据信息。如果不同则说明资源被改动过,则响应整个资源内容,返回状态码 200。

总结

通过前文,我们了解到 HTTP 缓存主要分强制缓存和协商缓存。强制缓存由 Cache-ControlExipres(HTTP1.0)控制。浏览器直接读本地缓存,不会再跟服务器端交互,状态码 200。协商缓存由 Last-Modified / IfModified-SinceEtag /If-None-Match实现,每次请求需要让服务器判断一下资源是否更新过,从而决定浏览器是否使用缓存,如果是,则返回 304,否则重新完整响应。

文档信息

版权声明:署名-非商业性使用-禁止演绎 4.0 国际(CC BY-NC-ND 4.0)

原文链接:https://www.liuxing.io/blog/http-cache/

发表日期:2021年04月26日


最近更新

Curl 使用指南

Curl 是一个常用的命令行数据传输工具, 可以方便的从命令行创建网络请求

2021年5月04日

JavaScript 中的分号

JavaScript 代码到底加不加分号。本文带你了解 JavaScript 的分号自动插入机制,以及哪些情况必须加分号

2021年5月02日