浏览器HTTP缓存机制

1,211 阅读7分钟

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的缓存机制