Cache of HTTP

Web缓存一般分为:

  • 数据库缓存:memcache和redis
  • 服务器缓存:缓存代理服务器或者代理缓存,存在客户端和服务器之间的中间服务器,属于公有缓存,降低服务器的负载压力。
  • 客户端缓存:一般是浏览器缓存

在这里我们讨论的是HTTP缓存

Note

浏览器缓存(Browser Caching)是为了节约网络的资源加速浏览,浏览器在用户磁盘上对最近请求过的文档进行存储,当访问者再次请求这个页面时,浏览器就可以从本地磁盘显示文档,这样就可以加速页面的阅览。

可以解决以下问题:

  • 冗余的数据传输
  • 带宽瓶颈
  • 距离延时

浏览器缓存主要有以下几种:

  • HTTP缓存
  • Web Storage
  • App Cache
  • IndexedDB
  • File System API

HTTP缓存即http协议缓存,浏览器根据http协议头部的特殊控制字段来控制资源缓存的机制。根据http协议版本的不同,用于控制缓存的头部字段主要分为HTTP1.0HTTP1.1

HTTP1.0

Pragma: no-cache: 通用首部,其是否使用缓存,与 Cache-Control: no-cache 效果一致。强制要求缓存服务器在返回缓存的版本之前将请求提交到源头服务器进行验证。

Expires: 响应头,标识缓存过期的日期,单位为秒,值为日期的格式,例如Thu, 01 Dec 1994 16:00:00 GMT

Pitfall

由于 Pragma 在 HTTP 响应中的行为没有确切规范,所以不能可靠替代 HTTP/1.1 中通用首部 Cache-Control,尽管在请求中,假如 Cache-Control 不存在的话,它的行为与 Cache-Control: no-cache 一致。建议只在需要兼容 HTTP/1.0 客户端的场合下应用 Pragma 首部。

由于 Expires 的值是一个具体的时间点,这个时间点是相对服务器的时间,所以客户端的时间和服务端的时间不一致,则这个过期时间的作用会有问题。

而HTTP缓存现在都是基于HTTP1.1的。

HTTP1.1

Age

Age 消息头里包含对象在缓存代理中存贮的时长,以秒为单位。.

Age 的值通常接近于 0。表示此对象刚刚从原始服务器获取不久;其他的值则是表示代理服务器当前的系统时间与此应答中的通用头 Date 的值之差。

Cache-Control

Cache-Control 通用消息头字段,被用于在 http 请求和响应中,通过指定指令来实现缓存机制。缓存指令是单向的,这意味着在请求中设置的指令,不一定被包含在响应中。

客户端缓存指令

Cache-Control: max-age=<seconds>
Cache-Control: max-stale[=<seconds>]
Cache-Control: min-fresh=<seconds>
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: no-transform
Cache-Control: only-if-cached

服务端缓存指令

Cache-control: must-revalidate
Cache-control: no-cache
Cache-control: no-store
Cache-control: no-transform
Cache-control: public
Cache-control: private
Cache-control: proxy-revalidate
Cache-Control: max-age=<seconds>
Cache-control: s-maxage=<seconds>

可缓存性

  • public: 表明响应可以被任何对象(包括:客户端或者代理服务器)缓存
  • private: 表明响应只能被单个用户缓存,不能作为共享缓存
  • no-store: 缓存不应存储有关客户端请求或者服务器响应的任何内容,不使用任何缓存。
  • no-cache: 在发布缓存副本之前,强制要求缓存把请求提交给原始服务器进行验证(协商缓存验证)。

到期

  • max-age=<seconds>: 设置缓存存储的最大周期(单位秒),超过这个时间缓存被认为过期,与 Expires 相反,时间是相对于请求的时间
  • s-maxage=<seconds>: 覆盖max-age或者Expires头,但是仅适用于共享缓存 (比如各个代理),私有缓存会忽略它。
  • max-stale[=<seconds>]: 表明客户端愿意接收一个过期的资源,可以设置一个可选的秒数,表示响应不能已经过时超过该给定的时间。
  • min-fresh=<seconds>: 表明客户端希望获取一个能在指定的秒数内保持其最新的状态的响应。

重新验证和重新加载

  • must-revalidate: 一旦资源过期(比如已经超过max-age),在成功向原始服务器验证之前,缓存不能用该资源响应后续请求。
  • proxy-revalidate: 与 must-revalidate 作用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略。

其他

  • no-transform: 不得对资源进行转换或转变。Content-Encoding、Content-Range、Content-Type等 HTTP 头不能由代理修改。
  • only-if-cached:表明客户端只接受已缓存的响应,并且不要向原始服务器检查是否有更新的拷贝。

Last-Modified/If-Modified-Since

Last-Modified 是一个响应首部,其中包含源头服务器认定的资源做出修改的日期及时间。它通常被用作一个验证器来判断接收到的或者存储的资源是否彼此一致。由于精确度比 ETag 要低,所以这是一个备用机制。包含有 If-Modified-SinceIf-Unmodified-Since 首部的条件请求会使用这个字段。

If-Modified-Since 是一个条件式请求首部,服务器只在所请求的资源在给定的日期时间之后对内容进行过修改的情况下才会将资源返回,状态码为 200 。如果请求的资源从那时起未经修改,那么返回一个不带有消息主体的 304 响应,而在 Last-Modified 首部中会带有上次修改时间。不同于 If-Unmodified-Since, If-Modified-Since 只可以用在 GET 或 HEAD 请求中。

当与 If-None-Match 一同出现时,它(If-Modified-Since)会被忽略掉,除非服务器不支持 If-None-Match

ETag/If-None-Match

ETag 响应头是资源的特定版本的标识符。这可以让缓存更高效,并节省带宽,因为如果内容没有改变,Web 服务器不需要发送完整的响应。而如果内容发生了变化,使用 ETag 有助于防止资源的同时更新相互覆盖(“空中碰撞”)。

如果给定 URL 中的资源更改,则一定要生成新的 ETag 值。比较这些 ETag 能快速确定此资源是否变化。

If-None-Match: 请求头部中的标识,当资源过期后,需要向服务器发送验证请求,如果过期的缓存副本中包含了Etag,那验证请求的头部会带上If-None-Match标识,值为缓存副本中 ETag 的值,服务器获取该值后与资源当前hash值做比较,如果相同,则返回304并更新相关缓存值,如果不相同,说明资源被修改过,则返回200和新资源,对应更新缓存。

Last-Modified 和 ETag

Cache-Control 用来控制缓存,而Last-ModifiedETag则是用来当缓存过期后,重新请求服务器验证时使用。

两者作用相同,为何有必要两者都存在呢?

  • 资源更新频繁:如果资源更新比较频繁,如果在1s内频繁更新,那么Last-Modified就没有办法知道资源已经更新了,因为Last-Modified的单位是
  • 特殊场景下,一些资源会被定时更新,但是其内容没有变,而Last-Modified却在变,导致缓存失效,没有达到缓存的目的。
  • 服务器的时间与代理服务器和客户端的时间不一致,时间偏差,导致缓存失效
  • ETag: 其值计算比较复杂且耗性能,此时使用Last-Modified比较合适

总而言之,各有各的好处,根据不同的场景,选择适合服务器的机制,或者两者都使用,当两这同时存在的时候,其ETag的优先级会比较高,先判断ETag,其次是Last-Modified,最后才决定返回200还是304

缓存处理流程

首次请求资源

首次请求,没有缓存规则,以Cache-Control: no-cache请求数据,服务返回资源以及缓存规则

二次请求

非首次请求,则需要判断是否使用还是重新请求新的资源。

这里首先查看 强缓存 是否有效,无效则查看 协商缓存

整体流程

https://zhuanlan.zhihu.com/p/90507417