详解浏览器HTTP强、协商缓存

私有缓存

私有缓存是绑定到特定客户端的缓存 ---通常是浏览器缓存。由于存储的响应不与其他客户端共享,因此私有缓存可以存储该用户的个性化响应

Cache-Control: private

  • 个性化内容通常是由 cookie 控制,但是 cookie 的存在并不能表明他是私有的,因此单独的 cookie 不会使响应成为私有的。
  • 需要注意的是,如果响应具有 Authorization 标头,则不能将其存储在私有缓存(或共享缓存,除非 Cache-Control 指定的是 public)。
    共享缓存
    共享缓存位于客户端和服务端之间,可以存储能在用户之间共享的响应。共享缓存可以细分为代理缓存和托管缓存
    代理缓存
    除了访问控制的功能外,一些代理还实现了缓存以减少网络流量。这通常不由服务开发人员管理,必须恰当的HTTP标头控制
    Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate
    托管缓存
    托管缓存由服务人员明确部署,以降低源服务器负载并有效地交付内容。示例包括:反向代理、CDN、service worker 与缓存 API 的组合。
    • 大多数情况下,你可以通过 Cache-Control 标头和你自己的配置文件或仪表板来控制缓存的行为
    • HTTP 缓存规范本质上没有定义显式删除缓存的方法 --- 但是使用托管缓存,可以通过仪表板操作、API 调用、重新启动等实时删除已经存储的响应。这允许更主动的缓存策略。
    • 也可以忽略HTTP 缓存闺房协议以支持显示操作。例如指定: Cache-Control: no-store 来退出私有缓存或代理缓存,同时使用你自己的策略尽在托管缓存中进行缓存。
    • 某些 CDN 提供自己的标头,这些仅对该CDN有效(如:Surrogate-Control )。目前正在努力定义一个 CDN-Cache-Control 标头来标准化这些标头
      启发式缓存
      HTTP 旨在尽可能多地缓存,因此即使没有给出 Cache-Control,如果满足某些条件,响应也会被存储和重用。这称为启发式缓存。
      启发式缓存是在 Cache-Control 被广泛采用之前出现的一种解决方法(未指定过期时间,客户端尝试性地重用数据,时间约为修改前至今的 10%的时间),基本上所有响应都应明确指定 Cache-Control 标头。
      基于 age 的缓存策略
      存储的 HTTP 响应有两种状态: fresh 和 stale 。fresh状态通常表示响应仍然有效,可以重用。而 stale 状态表示缓存的响应已经过期。
      确定响应何时是 fresh 的和何时是 stale 的标准是 age。在 HTTP 中,age 是自响应生成以来经过的时间。
      HTTP/1.1 200 OK
      Content-Type: text/html
      Content-Length: 1024
      Date: Tue, 22 Feb 2022 22:22:22 GMT
      Cache-Control: max-age=604800

<!doctype html>

...

存储示例响应的缓存会计算响应生成后经过的时间,并将结果用作响应的 age。

对于该示例的响应,max-age 的含义如下:

如果响应的 age 小于一周,则响应为 fresh。

如果响应的 age 超过一周,则响应为 stale。

只要存储的响应保持有效(fresh),它将用于兑现客户端请求。

Expires 或 max-age

  1. 在HTTP/1.0 中,有效期是通过 Expires 标头来指定的。

Expires 标头使用明确的时间来缓存生命周期

Expires: Tue, 28 Feb 2022 22:22:22 GMT

但是时间格式难以解析,也有可能通过故意偏移系统时钟来诱发问题,一般不使用

  1. 在HTTP/1.1 中,Cache-Control 采用了 max-age ---用于指定经过的时间

若Expires 和 Cache-Control: max-age 都可用,则将 max-age 定义为首选

验证响应

验证即为协商缓存:使用包含 If-Modified-Since 或 If-None-Match 请求标头的条件请求完成的。

强制验证:

  1. Cache-Control: no-cache

  2. Cache-Control: max-age=0, must-revalidate

max-age=0意味着立即过时,must-revalidate意味着一旦过时不得在没有重新验证的情况下重用它。

no-cache 指令不会阻止响应的存储,而是阻止在没有重新验证的情况下重用响应。

不使用缓存

Cache-Control: no-store

No-store 指令会禁止响应存储在任何缓存中

不与其他用户共享

Cache-Control: private;

重新加载、强制重新加载 与 避免重新验证

  1. 重新加载

GET / HTTP/1.1

Host: example.com

Cache-Control: max-age=0

If-None-Match: "deadbeef"

If-Modified-Since: Tue, 22 Feb 2022 20:20:20 GMT

// 注意:"reload"不是正常重新加载的正确模式;"no-cache"才是

fetch("/", { cache: "no-cache" });

  1. 强制重新加载

GET / HTTP/1.1

Host: example.com

Pragma: no-cache

Cache-Control: no-cache

Fetch标准

// 注意:"reload"------而不是"no-cache"------是"强制重新加载"的正确模式

fetch("/", { cache: "reload" });

  1. 避免重新验证

  2. 永远不会改变的内容应该被赋予一个较长的 max-age,方法是使用缓存破坏------也就是说,在请求 URL 中包含版本号、哈希值等。

  3. immutable 指令可用于明确指示不需要重新验证,因为内容永远不会改变。

Cache-Control: max-age=31536000, immutable

删除存储的响应

  • 基本没有办法删除用很长的 max-age 存储的响应。

  • 由于缓存,不再有请求到达服务器!

  • 规范中提到的方法之一是使用不安全的方法(例如:POST)发送对同一URL对请求,但对于许多客户端而言,通常很难故意这样做

  • 有一个 Clear-Site-Data: cache 标头和值的规范,但并非全部浏览器都支持。---即使使用它,他也只会影响浏览器缓存,而不会影响中间缓存。

  • 除非用户手动执行重新加载、强制重新加载或清除历史操作,否则存储的响应都将保留其 max-age 期间。

  • 缓存减少了对服务器的访问,也意味着服务器失去了对该URL的控制。可以通过添加 no-cache 以便服务器始终接受请求并发送预期的响应

请求折叠

共享缓存主要位于源服务器之前,旨在减少到源服务器的流量。

因此,如果多个相同的请求同时到达共享缓存,中间缓存将代表自己将单个请求转发到源,然后源就可以将结果重用于所有客户端。这称为请求折叠

请求同时到达时会发生请求折叠,因此即使响应中给出了 max-age=0 或 no-cache,它也会被重用

如果响应是针对特定用户个性化的,并且你不希望它在折叠中共享,则应添加 private 指令。

[图片]

常见的缓存模式

默认设置

缓存的默认行为(即对于没有 Cache-Control 的响应) 不是简单的"不缓存",而是根据所谓的"启发式缓存"进行隐式缓存。

为了避免"启发式缓存",最好显式地为所有响应提供一个默认的 Cache-Control 标头,一般为:

Cache-Control: no-cache

另外,如果服务实现了 cookie 或其他登录方式,并且内容是为每个用户个性化的,那么也必须提供 private ,以防止与其他用户共享:

Cache-Control: no-cache, private

缓存破坏

  1. 可以使用包含基于版本号或哈希值的更改部分的 URL 来提供 JS 和 CSS。

  2. 由于缓存根据他们的 URL 来区分资源,因此如果在更新资源时 URL 发生变化,缓存将不会在此被重用

  3. 缓存破坏是一种在内容更改时修改 URL 来使响应在很长一段时间内可缓存的技术。该技术可以应用于所有子资源,例如图像

主要资源

  1. 与子资源不同,主资源不能使用缓存破坏,因为他们的URL不能像子资源URL一样被修饰。

  2. 对于这种情况, no-cache 将是合适的

托管缓存的更多信息

  • 缓存主要资源很困难,因为仅使用 HTTP 缓存规范中的标准指令,在服务端上更新内容时无法主动删除缓存内容。

  • 但是,可以通过部署托管缓存( 比如 CDN 或 service worker )来实现。

  • Service Worker

相关推荐
m0_7482567810 分钟前
SpringBoot 依赖之Spring Web
前端·spring boot·spring
组合缺一11 分钟前
Solon v3.0.5 发布!(Spring 可以退休了吗?)
java·后端·spring·solon
栗豆包1 小时前
w118共享汽车管理系统
java·spring boot·后端·spring·tomcat·maven
武昌库里写JAVA7 小时前
【MySQL】7.0 入门学习(七)——MySQL基本指令:帮助、清除输入、查询等
spring boot·spring·毕业设计·layui·课程设计
栗子~~11 小时前
集成 jacoco 插件,查看单元测试覆盖率
缓存·单元测试·log4j
初晴~12 小时前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
唐 城13 小时前
curl 放弃对 Hyper Rust HTTP 后端的支持
开发语言·http·rust
雷神乐乐14 小时前
Spring学习(一)——Sping-XML
java·学习·spring
小林coding14 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云
DevilHeart灬14 小时前
使用Grafana中按钮插件实现收发HTTP请求
http·grafana