共计 2883 个字符,预计需要花费 8 分钟才能阅读完成。
http 缓存机制是一种 web 性能优化的手段,对于从事 web 行业的我们很有必要去弄懂,起初我仅仅只是知道浏览器会对请求的静态文件进行缓存,至于如何缓存,为什么缓存并不知其所以然。在这里结合自己所学及理解,用简短的文字来说明。
我们在访问百度首页的时候,会发现不管怎么刷新页面,静态资源基本都是返回 200(from cache)?200?缓存状态不应该是 304?
让我们带着疑问继续看下去
浏览器加载一个页面的流程:
- 浏览器先根据这个资源的 http 头信息来判断是否命中强缓存。如果命中则直接加在缓存中的资源,并不会将请求发送到服务器
- 如果未命中强缓存,则浏览器会将资源加载请求发送到服务器。服务器来判断浏览器本地缓存是否失效。若可以使用,则服务器并不会返回资源信息,浏览器继续从缓存加载资源
- 如果未命中协商缓存,则服务器会将完整的资源返回给浏览器,浏览器加载新资源,并更新缓存
强缓存
命中强缓存时,浏览器并不会将请求发送给服务器。在 Chrome 的开发者工具中看到 http 的返回码是 200,但是在 Size 列会显示为 (from cache)。
Expires
- Expires 是 http1.0 时代处理缓存的方式,用来启用缓存和定义缓存时间。Expires 的值对应一个 GMT(格林尼治时间),比如“Wed, 24 Jun 2020 03:33:06 GMT”来告诉浏览器资源缓存过期时间,如果还没过该时间点则不发请求。
- 与之对应的有 Pragma 字段,当该字段值为“no-cache”的时候禁止缓存,即每次都发送新的请求。
- 需要注意的是 Pragma 字段的优先级会更高,也就是说当 Pragma 设置为禁用,又给 Expires 定义一个还未到期的时间,这是还是会发起新的请求
- 缺点:响应报文中 Expires 所定义的缓存时间是相对服务器上的时间而言的,如果客户端上的时间跟服务器上的时间不一致,那缓存时间可能就没意义了
Cache-Control
上述的 Expires 时间是相对服务器而言,无法保证和客户端时间统一,http1.1 新增了 Cache-Control 来定义缓存过期时间, 若报文中同时出现了 Pragma、Expires 和 Cache-Control,会以 Cache-Control 为准
作为 Request Headres 时候可取值
- no-cache > 告知服务器不直接使用缓存,要求原服务器发送请求
- no-store > 所有内容都不会被保存到缓存或 Internet 临时文件中
- max-age=delta-seconds > 告知服务器客户端希望接收一个存在时间(Age)不大于 delta-seconds 秒的资源
- max-stale [= delta-seconds] > 告知服务器客户端愿意接收一个超过缓存时间的资源,若有定义 delta-seconds 则为 delta-seconds 秒,若没有定义则为任意超出时间
- min-fresh=delta-seconds > 告知服务器客户端希望接收一个在小于 delta-seconds 秒内被更新过的资源
作为 Response Headres 时候可取值
- public > 表明任何情况下都得缓存该资源(即使是需要 http 认证的资源)
- private[=”field-name”] > 表明返回报文中全部或部分(若指定了 field-name 则为 field-name 的字段数据)仅开放给部分用户(服务器指定的 share-user)做缓存使用,其他用户则不能缓存这些数据
- no-cache > 不直接使用缓存,要求向服务器发起(新鲜度校验)请求
- no-store > 所有内容都不会被保存到缓存或 Internet 临时文件中
- no-transform > 告知客户端缓存文件时不得对实体数据做任何更改
- max-age=delta-seconds > 告知客户端该资源在 delta-seconds 秒内是新鲜的,无需向服务器发送请求
协商缓存
若未命中强缓存,则浏览器会将请求发送至服务器。服务器根据 http 头信息中的 Last-Modify/If-Modify-Since 或 Etag/If-None-Match 来判断是否命中协商缓存。如果命中,则 http 返回码为 304,浏览器从缓存中加载资源。
Last-Modified
- 表示资源的最后更改时间,服务器将资源传递给客户端时,会将资源的最后更改时间以:
Last-Modified: GMT
的形式加在实体首部上一起返回给客户端 - 客户端会为资源标记上该信息,下次再次请求时,会把该信息附带在请求报文中一并带给服务器去做检查,若传递的时间值与服务器上该资源最终修改时间是一致的,则说明该资源没有被修改过,直接返回 304 状态码
If-Modified-Since
示例:If-Modified-Since: Wed, 24 Jun 2020 03:33:06 GMT
该请求首部告诉服务器如果客户端传来的最后修改时间与服务器上的一致,则直接回送 304 和响应报头即可。
If-Unmodified-Since
告诉服务器,若 Last-Modified 没有匹配上(资源在服务端的最后更新时间改变了),则应当返回 412(Precondition Failed) 状态码给客户端。
当遇到下面情况时,If-Unmodified-Since 字段会被忽略
- Last-Modified 值对上了(资源在服务端没有新的修改)
- 服务端需返回 2XX 和 412 之外的状态码
- 传来的指定日期不合法
ETag
- 服务器会通过某种算法,给资源计算出一个唯一标志(比如 md5 标志),在把资源响应给客户端的时候,会在实体首部加上“ETag: 唯一标识符”一起返回给客户端
- 客户端会保留该 ETag 字段,并在下一次请求时将其一并带过去给服务器。服务器只需要比较客户端传来的 ETag 跟自己服务器上该资源的 ETag 是否一致,就能很好地判断资源相对客户端而言是否被修改过了
- 如果服务器发现 ETag 匹配不上,那么直接以常规 GET 200 回包形式将新的资源(当然也包括了新的 ETag)发给客户端;如果 ETag 是一致的,则直接返回 304 知会客户端直接使用本地缓存即可。
If-None-Match
示例为 If-None-Match: “56fcccc8-1699”
告诉服务端如果 ETag 没匹配上需要重发资源数据,否则直接回送 304 和响应报头
If-Match
告诉服务器如果没有匹配到 ETag,或者收到了“*”值而当前并没有该资源实体,则应当返回 412(Precondition Failed) 状态码给客户端。否则服务器直接忽略该字段
如果 Last-Modified 和 ETag 同时被使用,则要求它们的验证都必须通过才会返回 304,若其中某个验证没通过,则服务器会按常规返回资源实体及 200 状态码
注意
Last-Modified 说好却也不是特别好,因为如果在服务器上,一个资源被修改了,但其实际内容根本没发生改变,会因为 Last-Modified 时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源). 为了解决 Last-Modified 可能存在的不准确的问题,Http1.1 推出了 ETag 实体首部字段。
参考: 浅谈浏览器 http 的缓存机制