什么是HTTP缓存

什么是 HTTP 缓存

我们为什么需要缓存（cache）？

我们知道，我们每次访问一个网页都需要加载资源，如果这些资源都从远端的服务器上加载，整个请求和响应时间会很长，有没有快一点的办法呢？有，可以从附近的 CDN 请求资源，减少了长距离的网络传输，响应速度自然就提升了。可是，CDN 还是离客户端有点远，有没有办法在浏览器缓存数据，尽量少的向外请求资源呢？其实这些思想都是缓存的思想，在每个节点都做缓存就可以极大的减少远端资源请求，加快响应速度。

缓存中有几个概念

Stale Content：陈旧的内容，即被缓存且过期的内容

Fresh Content：新鲜的内容，即被缓存且未过期的内容

Cache Validation：是联系服务器以检查缓存内容的有效性，并在其即将过期时得到更新的过程。

Cache Invalidation：是删除缓存中可用的任何陈旧内容的过程。

缓存按照位置可以分为浏览器缓存，代理缓存，反向代理缓存

浏览器缓存

浏览器缓存仅限于一个用户，并且与其他缓存不相同，并且它可以存储私有的响应。

代理缓存

与服务于单个用户的浏览器缓存不同，代理缓存可以服务于访问相同内容的数百个不同用户。往往是由 ISP 实施的。

反向代理缓存

反向代理缓存一般部署在服务端，用于减轻服务器的负载。

缓存头

每当服务器发出一些响应时，它都会伴随一些 HTTP 标头，以指导缓存与否以及如何缓存此响应

Expires

在 HTTP/1.1 和引入缓存控制之前，有一个 Expires 标头，它只是一个时间戳，告诉缓存应该将某些内容视为新鲜的时间。比如

Copy
Expires: Mon, 13 Mar 2017 12:22:00 GMT

当然，缓存上的时钟必须与服务器上的时钟同步，否则可能无法实现所需的结果。

尽管 Expires 还在使用，但我们现在应该优先考虑Cache-Control

Pragma

这也是来自 HTTP/1.1 的旧特性，我们可能会看到在这里和那里使用Pragma: no-cache，希望阻止响应被缓存。

Cache-Control

缓存控制（Cache-Control）规定了内容应被缓存多长时间和以何种方式缓存。

cache-control 可以包含多个值，

private

将缓存设置为私有意味着该内容不会在任何代理中被缓存，它只会被客户端（即浏览器）缓存。

public

如果设置为公开，除了被客户端缓存外，还可以被代理机构缓存；为许多其他用户服务。

no-store

指定该内容不被任何一个缓存所缓存。

no-cache

no-cache 表示可以保持缓存，但缓存的内容在被提供之前要从服务器上重新验证（例如，使用 ETag）。也就是说，仍然有一个对服务器的请求，但是是为了验证，而不是为了下载缓存的内容。

max-age: seconds

max-age 规定了内容被缓存的秒数。例如，如果 cache-control 看起来像下面这样。

Copy
cache-control: max-age=900, private

s-maxage: seconds

s-maxage 这里 s - 前缀代表共享。这条指令专门针对共享缓存。像 max-age 一样，它也可以得到一些东西被缓存的秒数。如果存在，它将覆盖共享缓存的 max-age 和 expires 头文件。

must-revalidate

must-revalidate 有时可能发生的情况是，如果你有网络问题，而内容无法从服务器检索，浏览器可能会在没有验证的情况下提供陈旧的内容。 must-revalidate 可以避免这种情况。如果这个指令存在，意味着在任何情况下都不能提供陈旧的内容，在提供数据之前必须从服务器上重新验证。

proxy-revalidate

proxy-revalidate 与 must-revalidate 类似，但它对共享或代理缓存的规定是一样的。换句话说，proxy-revalidate 对于 must-revalidate 来说就像 s-maxage 对于 max-age 一样。

Mixing Values

我们可以以不同的方式组合这些指令以实现不同的缓存行为，但是no-cache/no-store和public/private是互斥的。如果同时指定no-store和no-cache，则no-store将优先于no-cache。

Validators

到目前为止，我们只讨论了内容是如何被缓存的，以及多长时间的缓存内容被认为是新鲜的，但我们没有讨论客户端如何从服务器上进行验证。下面我们讨论用于此目的的头信息。

Etag

Etag 或 "Entity Tag" 是在 HTTP/1.1 规范中引入的。Etag 只是一个独特的标识符，服务器将其与一些资源联系在一起。这个 ETag 后来被客户端用来做有条件的 HTTP 请求，说明 "如果 ETag 与我的 ETag 不一样，就给我这个资源"，只有在 ETag 不匹配的情况下才会下载内容。

生成 ETag 的方法在 HTTP 文档中没有指定，通常使用一些抗碰撞的哈希函数来给资源的每个版本分配 ETag。可以有两种类型的 etag，即强和弱的 etag。

Copy
ETag: "j82j8232ha7sdh0q2882" - Strong Etag
ETag: W/"j82j8232ha7sdh0q2882" - Weak Etag (prefixed with `W/`)

强验证 ETag 意味着两个资源是完全相同的，它们之间根本没有区别。而一个弱的 ETag 意味着两个资源虽然严格意义上不一样，但可以被认为是一样的。例如，弱的 ETag 可能对动态内容很有用。

现在我们知道什么是 Etag 了，但浏览器是如何提出这个请求的呢？ 通过向服务器提出请求，同时在If-None-Match头中发送可用的 Etag。

考虑一下这样的情况：你打开了一个网页，其中加载了一个标志图片，缓存期为 60 秒，ETag 为abc123xyz。大约 30 分钟后，你重新加载该网页，浏览器会注意到 60 秒内新鲜的标志现在已经过时了；它将触发一个请求给服务器，在 if-none-match 头中发送过时的标志图像的 ETag

Copy
If-None-Match: "abc123xyz"

然后，服务器将比较这个 ETag 和资源的当前版本的 ETag。如果两个 ETag 都匹配，服务器将发回 304 未修改的响应，这将告诉客户，它所拥有的副本仍然是好的，它将被视为新的 60 秒。如果两个标志都不匹配，即标志很可能已经改变，客户端将被发送新的标志，它将用来替换它所拥有的陈旧的标志。

Last-Modified

服务器可能包括Last-Modified标头，表明一些内容最后被修改的日期和时间。

Copy
Last-Modified: Wed, 15 Mar 2017 12:30:26 GMT

当内容过期时，客户端将向服务器发出一个条件性请求，其中包括它在名为If-Modified-Since的头中的最后修改日期，以获得更新的 Last-Modified 日期；如果它与客户端的日期相匹配，内容的Last-Modified日期将被更新，以便在另外的 n 秒内被视为新鲜。如果收到的Last-Modified日期与客户拥有的日期不一致，则从服务器重新加载内容，并替换为客户拥有的内容。

Copy
If-Modified-Since: Wed, 15 Mar 2017 12:30:26 GMT

你现在可能会问，如果缓存的内容既有Last-Modified，又有ETag，怎么办？那么，在这种情况下，两者都要使用，也就是说，当且仅当 ETag 与新检索的内容相匹配，并且 Last-Modified 日期也相匹配时，将不会有资源重新下载。如果 ETag 不匹配或者 Last-Modified 日期大于服务器上的日期，内容就必须重新下载。