浏览器缓存机制详解

HTTP 请求 Status Code 层级关系

200:当浏览器本地没有缓存或者下一层失效时,或者用户进行了硬性重新加载,浏览器会直接去服务器下载最新数据

304:这一层由 Last-modified/ETag 控制。当下一层失效或者用户点击刷新时,浏览器就会发送请求给服务器,如果服务器端没有变化,则返回 304 给浏览器

200(from caceh):这一层由 Expires/Cache-Control控制,Expires 是绝对时间,Cache-control 是相对时间,两者都存在时,Cache-control 覆盖 Expires,只要没有失效,浏览器只访问自己的缓存

下面来看一个实际的请求

Last-Modified

Last-Modified 是 header 中的一个参数, 这是服务器自动加上的。在浏览器第一次请求某一个 URL 时,服务器端返回 200 状态,同时 Response Headers 会有一个 Last-Modified 属性用于标记此文件在服务器端最后被修改时间。

再次访问该资源的时候,浏览器会通过 Request Headers 里向服务器发送 If-Modified-Since,询问该文件是否被修改过。

如果服务器端返回 304 状态,就表示该资源没有改变,浏览器可以使用本地 cache。

PS:如果 If-Modified-Since 的时间比服务器当前时间(当前的请求时间request_time)还晚,会认为是个非法请求

ETag

ETag 是一个可选的响应参数,由服务器根据资源的特定版本而指定,HTTP规范从未指定生成ETag的方法,通常是文件内容的哈希值或者其他指纹码。简单理解就是服务器响应时给请求资源的标记,并放在 Response Headers 返回给客户端。

浏览器发送的查询参数是这样的。

如果响应的 Etag 仍然一致,说明资源未被修改,则返回 304 状态,浏览器可以使用本地 cache。

Last-Modified 和 ETag 同时使用

服务器先判断 Last-modified 再判断 ETag, 必须都没有过期,才能返回 304。

分布式系统里多台机器间文件的 Last-Modified 必须保持一致,以免负载均衡到不同机器导致比对失败

分布式系统尽量关闭掉 ETag (每台机器生成的ETag都会不一样)

P.S. 关于 Etag 还有个有趣的应用 http://lucb1e.com/rp/cookielesscookies/

Expires

Response Headers 里的 Expires 表示资源的过期时间,下一次发起请求时,如果请求时间晚于 Expires 给的日期时间,则认为这个响应式过时的,需要重新向服务器发送新请求。

如果希望某资源缓存失效,就在响应头里把 Expires 值设置为 -1。

Expires 配合 Last-Modified 或者 ETag 一起使用,Expires 未过期前浏览器不发送真实请求,Expires 过期或用户刷新时则有 Last-Modified/ETag 来向服务器查询。

Cache-Control

Expires 的一个缺点就是,返回的到期时间是服务器端的时间,如果客户端的时间与服务器的时间相差很大,那么误差就很大。

所以在HTTP 1.1版开始,使用Cache-Control: max-age=秒替代,当前的所有浏览器都支持 Cache-Control。

Cache-Control 可以控制每个响应资源在什么条件在需要缓存多长时间。

格式:Cache-Control:cache-directive

cache-directive可选值:

Request:

| "no-cache"
| "no-store"
| "max-age" "=" delta-seconds
| "max-stale" [ "=" delta-seconds ]
| "min-fresh" "=" delta-seconds
| "no-transform"
| "only-if-cached"
| "cache-extension"

Response:

| "public"
| "private" [ "=" <"> field-name <"> ]
| "no-cache" [ "=" <"> field-name <"> ]
| "no-store"
| "no-transform"
| "must-revalidate"
| "proxy-revalidate"
| "max-age" "=" delta-seconds
| "s-maxage" "=" delta-seconds
| "cache-extension"

no-cache: 必须先与服务器确认返回的响应是否被更改,如果存在Last-Modified / ETag,no-cache 会发起往返通信来验证缓存,如果资源未修改,可以避免下载。

no-store:直接进制浏览器和所有中继缓存存储返回的任何版本响应,每次用户请求该资源的时候,都会向服务器发送请求,每次都会下载完整的响应。

max-age:从请求开始时间,允许响应被缓存的最大时间(秒数)。

public:告知任何缓存者,可以无条件的缓存该响应。

private: 仅为单个用户响应消息,不能被共享缓存。

废弃和更新已缓存的响应

既然我们已经知道了浏览器会缓存响应的资源,那么如果已经在 Cache-Control 中告诉客户端某个combined.min.css 文件 max-age = 2592000,但这个时候开发修改了这个文件,并希望立即在线上生效,如何告诉客户端文件已经过期。实际上,在不修改资源 URL 的情况下,这是做不到的。

所以我们要欺骗浏览器,就是通过在文件名中嵌入指纹码来实现。当资源 URL 被修改后,浏览器就会认为这是一个新的资源,发送一个新的请求去下载它。

例如:combined.min-1fe2dacb.css

另一种方法,多用于更新图片缓存,不改变文件名本身,仅为 URL 添加?v={指纹码}

例如:background: url(/assets/global/upyun_logo.svg?v=5ff3a62f)

Chrome DevTools 中的 disable cache 是如何实现的?

你可能在开发中使用过 Chrome DevTools 的 disable cache 勾选项来禁止浏览器缓存响应内容,强制每次请求都获取最新的内容,那么你知道 Chrome 的 disable cache 实际上是如何实现的吗?

先看一个正常的请求头

再看 disable cache 被勾选时的请求

通过对比两个请求头的内容,可以发现它们之间的区别:

  • 正常请求头中的两个属性 If-Modified-SinceIf-None-Match 在 disable cache 的请求头中被去掉了
  • disable cache 的请求头多了 Cache-Control: no-cache 属性和 Pragma: no-cache 属性

通过前面我们了解到的缓存层级关系来分析, Cache-Control 被设置为 no-cache 后,能够禁止 200(from caceh) 这层的缓存,然后进入 Last-modified/ETag 控制的这层,而我们又留意到 disable cache 把 If-Modified-SinceIf-None-Macth 同时从请求头去除了,此刻 304 这层的缓存也被禁止了。至此浏览器就会被强制要求去服务器下载最新的数据。

注意:由于 Pragma 在 HTTP 响应中的行为没有确切规范,所以不能可靠替代 HTTP/1.1 中通用首部 Cache-Control,尽管在请求中,假如 Cache-Control 不存在的话,它的行为与 Cache-Control: no-cache 一致。建议只在需要兼容 HTTP/1.0 客户端的场合下应用 Pragma 首部。

results matching ""

    No results matching ""