# HTTP 缓存技术

HTTP 缓存有两种实现方式，分别是<mark style="color:blue;">**强制缓存**</mark>和<mark style="color:blue;">**协商缓存**</mark>。

## 强制缓存 <a href="#shen-me-shi-qiang-zhi-huan-cun" id="shen-me-shi-qiang-zhi-huan-cun"></a>

**强制缓存指的是只要浏览器判断缓存没有过期，则直接使用浏览器的本地缓存**，决定是否使用缓存的主动性在于浏览器这边。

强制缓存是利用下面这两个 **HTTP 响应头部（Response Header）**&#x5B57;段实现的，它们都用来表示资源在客户端缓存的有效期：

* **Cache-Control**，是一个相对时间；
* **Expires**，是一个绝对时间；

> 如果 HTTP 响应头部同时有 Cache-Control 和 Expires 字段的话，**Cache-Control 的优先级高于  Expires。**

具体的实现流程如下：

* 当浏览器第一次请求访问服务器资源时，服务器会在返回这个资源的同时，在 Response 头部加上 Cache-Control，Cache-Control 中设置了过期时间；
* 浏览器再次请求访问服务器中的该资源时，会**先比较 Cache-Control 中设置的过期时间，判断该资源是否过期**，如果没有，则使用该缓存，否则重新请求服务器；
* 服务器再次收到请求后，会再次发送 Response 头部 Cache-Control。

## 协商缓存 <a href="#shen-me-shi-xie-shang-huan-cun" id="shen-me-shi-xie-shang-huan-cun"></a>

304 响应码告诉浏览器可以使用本地缓存的资源，通常这种通过服务端告知客户端是否可以使用缓存的方式被称为**协商缓存**。

协商缓存可以基于两种头部来实现。

* 第一种：请求头部中的 If-Modified-Since 字段与响应头部中的 Last-Modified 字段实现，这两个字段的意思是：

  * 响应头部中的 Last-Modified：标示这个响应资源的最后修改时间；
  * 请求头部中的 If-Modified-Since：最近一次收到的服务端返回的响应中的 Last-Modified；

  服务器收到请求后发现有 If-Modified-Since 则与被请求资源的最后修改时间进行对比（Last-Modified）

  * 如果最后修改时间较新（大），说明资源又被改过，则返回最新资源，HTTP 200 OK；
  * 如果最后修改时间较旧（小），说明资源无新修改，响应 HTTP 304 走缓存。
* 第二种：请求头部中的 If-None-Match 字段与响应头部中的 ETag 字段，这两个字段的意思是：

  * 响应头部中 Etag：唯一标识响应资源；
  * 请求头部中的 If-None-Match：最近一次收到的服务端返回的响应中的 Etag；

  服务器收到请求后对 If-None-Match 与请求资源的 Etag 进行比对，如果资源没有变化返回 304，如果资源变化了返回 200。

第一种实现方式是基于时间实现的，第二种实现方式是基于一个唯一标识实现的，相对来说后者可以更加准确地判断文件内容是否被修改，避免由于时间篡改导致的不可靠问题。

> ETag 的优先级比 Last-Modified 的优先级高，这是因为 ETag 能解决 Last-Modified 几个比较难以解决的问题：
>
> * 在没有修改文件内容情况下文件的最后修改时间可能也会改变，这会导致客户端认为这文件被改动了，从而重新请求；
> * 可能有些文件是在秒级以内修改的，If-Modified-Since 能检查到的粒度是秒级的，使用 Etag就能够保证这种需求下客户端在 1 秒内能刷新多次；
> * 有些服务器不能精确获取文件的最后修改时间。

{% hint style="warning" %}

## <mark style="color:orange;">注意</mark>

**协商缓存这两个字段都需要配合强制缓存中 Cache-Control 字段来使用，**<mark style="color:orange;">**只有在未能命中强制缓存的时候，才能发起带有协商缓存字段的请求**</mark><mark style="color:orange;">。</mark>

当使用 ETag 字段实现的协商缓存的过程：

* 当浏览器第一次请求访问服务器资源时，服务器会在返回这个资源的同时，在 Response 头部加上 ETag 唯一标识，这个唯一标识的值是根据当前请求的资源生成的；
* 当浏览器再次请求访问服务器中的该资源时，**首先会先检查强制缓存是否过期**：
  * 如果没有过期，则直接使用本地缓存；
  * 如果缓存过期了，会在 Request 头部加上 If-None-Match 字段，该字段的值就是 ETag 唯一标识；
* 服务器再次收到请求后，**会根据请求中的 If-None-Match 值与当前请求的资源生成的唯一标识进行比较**：
  * **如果值相等，则返回 304 Not Modified，不会返回资源**；
  * 如果不相等，则返回 200 状态码和返回资源，并在 Response 头部加上新的 ETag 唯一标识；
* 如果浏览器收到 304 的请求响应状态码，则会从本地缓存中加载资源，否则更新资源。
  {% endhint %}
