HTTP 缓存机制

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

HTTP 缓存策略有两种:

  1. 强缓存
  2. 协商缓存

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

强缓存

强缓存不会向服务器发送请求,直接从缓存中读取资源,在 chrome 控制台的 Network 选项中可以看到该请求返回 200 的状态码,并且 Size 显示 “from disk cache” 或 “from memory cache”。
强缓存可以通过设置 Expires 和 Cache-Control 两种响应头来控制缓存。如果同时存在,Cache-Control 优先级高于 Expires。

Expires

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

1
Expires: Wed, 21 Oct 2015 07:28:00 GMT
Cache-Control

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

1
Cache-Control: max-age=315360000

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

可缓存性
  • public:表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存;
  • private:表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它);
  • no-cache:不使用强缓存,需要与服务器验协商缓存验证;
  • no-store:缓存不应存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存。
    过期
  • max-age=:缓存存储的最大周期,超过这个周期被认为过期;
  • s-max-age=:设置共享缓存。会覆盖 max-age 和 expires,私有缓存会忽略它;
  • max-stale=:客户端愿意接收一个已经过期的资源,可以设置一个可选的秒数,表示响应不能已经过时超过该给定的时间;
  • min-fresh=:客户端希望在指定的时间内获取最新的响应重新验证和重新加载;
  • must-revalidate:如页面过期,则去服务器进行获取;
  • proxy-revalidate:与 must-revalidate 作用相同,但是用于共享缓存。
    其它
  • only-if-cached:不进行网络请求,完全只使用缓存;
  • no-transform:不得对资源进行转换和转变。例如,不得对图像格式进行转换。

协商缓存

协商缓存会先向服务器发送一个请求,服务器会根据这个请求的 request header 的一些参数来判断是否命中协商缓存,如果命中,则返回 304 状态码并带上新的 response header 通知浏览器从缓存中读取资源。
协商缓存通过 Last-Modified/If-Modified-Since 和 ETag/If-None-Match 这两对 Header 来控制缓存。
If-Modified-Since 和 If-None-Match 存在于请求头中,Last-Modified 和 ETag 存在于响应头中。

Last-Modified/If-Modified-Since

在服务器响应请求时,会通过 Last-Modified 告诉浏览器资源的最后修改时间。
在未命中强缓存的情况下,浏览器会再次请求服务器,请求头会包含 if-Modified-Since 字段,后面跟着在缓存中获得的最后修改时间(即上次返回的 Last-Modified 值)。
服务端收到此请求头发现有 if-Modified-Since,则与被请求资源的最后修改时间进行对比,如果一致则返回 304 和响应报文头,浏览器只需要从缓存中获取信息即可。如果已经修改,那么返回全新的资源文件和响应头,服务器返回:200。
但是在服务器上经常会出现这种情况,一个资源被修改了,但其实际内容根本没发生改变,会因为 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。