首页

Javascript

Html
Css
Node.js
Electron
移动开发
小程序
工具类
服务端
浏览器相关
前端收藏
其他
关于
公司注册

深入学习Cache-Control

2019年03月15日 翻译 阅读(3392) 作者:Jerman

写在最前面,非常感谢Harry(不认识,哈哈)总结的这一篇文章,看了后对于Cache Control的理解是醍醐灌顶。


📊您对缓存和Cache-Control标头的了解程度如何?

- Harry Roberts(@csswizardry,2019年3月3日


Cache-Control

管理资源缓存的最常用和最有效的方法之一是通过 HTTP Header中的 Cache-ControlCache-Control Header头适用于单个资源,这意味着我们页面上的所有内容都可以具有非常精准的缓存策略。这授予的控制量使得非常复杂和强大的缓存策略成为可能。

一个 Cache-Control 头可能会是这个样子:

  1. Cache-Control: public, max-age=31536000

Cache-Control 是名称,publicmax-age=31536000指令Cache-Control 可以接受一个或多个指令,这些指令的真正意义,及最佳的使用场景,是我这篇文章要讲的。


publicprivate

public 意味着任何缓存都可以存储响应的副本。 这包括CDN,代理服务器等。 public 指令通常是多余的,因为其他指令(例如 max-age )的存在已经隐性的表示可以缓存副本了。

private 是一个显式指令,只有响应的最终接收者( 客户端浏览器 )可以存储该文件的副本。 虽然private本身并不是一个安全功能,它主要用于防止公共缓存(如cdn)存储包含一个用户敏感信息的的响应。


max-age

max-age 定义响应在一定时间内可以使用缓存(相对于请求的时间),而不是重新拉取最新文件。单位为

  1. Cache-Control: max-age=60

Cache-Control 告诉浏览器它可以在接下来的60秒内使用浏览器缓存中的此文件,而不用重新去服务器取。 60秒后再次访问,浏览器将从服务器拉取最新文件。

如果服务器有一个新文件供浏览器下载,它将响应 200 响应,下载新文件,旧文件将从HTTP缓存中弹出,新文件将替换它,并将遵循其缓存头(将继续使用Cache-Control: max-age=60)。

如果服务器没有需要下载的更新副本,则服务器会响应 304 响应,不需要下载任何新文件,并将使用新标头更新缓存副本。 这意味着,如果 Cache-Control: max-age=60 header头仍然存在,则缓存文件的60秒将再次启动。 一个文件的总缓存时间为120秒。

注意: 有一个非常大的警告 max-age 本身是告诉浏览器资源是过时的,但它并没有告诉浏览器,它绝对不能使用过时的版本。 浏览器可以使用自己的启发式方法来决定是否在不重新验证的情况下使用过时的版本。 这种行为有点不确定,因此很难确切知道浏览器实际上会做什么。 为此,我们有一系列更明确的指令,可以和max-age 一起使用。 感谢 Andy Davies 帮助我澄清这一点。

s-maxage

s-maxage (注意:maxage之间缺少的 -)在公共缓存中(如代理层、网关、CDN),将优先于 max-age 指令。 使用 max-ages-maxage 结合使您可以分别为私有和公共缓存提供不同的缓存持续时间。与max-age的区别是:max-age用于普通缓存,而s-maxage用于代理缓存。如果存在s-maxage,则会覆盖max-ageExpires.

no-store

  1. Cache-Control: no-store

如果我们不想缓存文件怎么办? 如果文件包含敏感信息怎么办? 也许这是一个包含银行详细信息的HTML页面? 或许这些信息对时间至关重要? 也许是一个包含实时股票价格的页面? 我们不想在缓存中存储或提供这样的任何响应:我们总是希望丢弃敏感信息并获取最新的实时信息。 我们可以用 no-store

no-store 是一个非常强的指令,不会将任何信息持久存储在私有或其他任何缓存中。 带有no-store指令的请求,任何情况下都会向服务器拉取最新文件。

no-cache

  1. Cache-Control: no-cache

这是一个让大多数人感到困惑的地方…… no-cache 并不意味着“没有缓存”。 正直意思是“在服务器重新验证缓存副本并且服务器说可以使用缓存副本之前,不要使用缓存”。 这听起来应该叫 must-revalidate(必须重新验证) ! 但听起来也不是样。

no-cache 实际上是一种非常聪明的方式,始终保证最新的内容,但如果可能的话,还能够使用更快的缓存副本。 no-cache 将始终请求网络,因为它必须重新验证服务器才能释放浏览器的缓存副本(除非服务器响应更快的响应),但如果服务器响应良好,则网络传输只是一个文件的头:主体从缓存中获取,而不是重新下载。

因此,就像我说的,这是一种结合最新文件和从缓存中获取文件的可能性的智能方法,但是它至少会在网络上得到一个HTTP响应头。

no-cache 几乎任何动态HTML页面都是一个很好的用例 。 想想新闻网站的主页:它不是实时的,也不包含任何敏感信息,但理想情况下我们希望页面始终显示最新鲜的内容。 我们可以 cache-control: no-cache 用来指示浏览器首先检查服务器,如果服务器没有更新的提供( 304 ),让我们重用缓存版本。 如果服务器确实有一些更新的内容,它将作为这样的响应( 200 )并发送更新的文件。

提示:max-age 指令与指令一起 发送是没有用的, no-cache 因为重新验证的时间限制为零秒。


must-revalidate

更令人困惑的是,虽然上面的声音听起来应该被称为 must-revalidate ,但结果 must-revalidate 却是不同的东西(但仍然相似)。

  1. Cache-Control: must-revalidate, max-age=600

must-revalidate 需要一个相关的 max-age 指令; 上面,我们把它设置为十分钟。

前面讲的no-cache 会立即重新验证服务器,并且如果服务器说它可以用缓存副本才可以使用缓存 , must-revalidate 就像 no-cache 宽限期 一样 。 这里发生的事情是,在前十分钟,浏览器 不会 与服务器重新验证,但是在十分钟过去的那一刻,它又会向服务器重新验证。 如果服务器没有任何更新内容,它会响应304 并且更新 Cache-Control 头应用于缓存文件。然后再次开始十分钟,如果在十分钟之后,服务器上有一个较新的文件,我们会得到 200 响应及其正文,并且本地缓存会更新。

我的博客是must-revalidate很好的一个例子:很少改变的静态页面。 当然,最新的内容是可取的,但考虑到我的网站不经常发生变化,我们不需要像手一样笨重的东西 no-cache 。 相反,让我们假设一切都足够好十分钟,然后再重新验证。

proxy-revalidate

与此类似 s-maxageproxy-revalidate 是public-cache特定版本的 must-revalidate 。 它被私人缓存忽略了。


immutable

immutable 是一个非常新的非常整洁的指令,它告诉浏览器更多关于我们发送它的文件类型 - 它的内容是可变的还是不可变的? 但是,在我们看一下之前 immutable ,让 我们看看 它正在解决的问题:

用户刷新会导致浏览器重新验证文件,无论其新鲜度如何,因为用户刷新通常意味着以下两种情况之一:

  1. 页面看起来很破碎;
  2. 内容看起来过时了……

…所以让我们检查服务器上是否有更新的内容。

如果服务器上有更新的文件,我们肯定想下载它。 因此,我们将得到一个 200 响应,一个新文件,并且 - 希望 - 问题得到解决。 但是,如果服务器上没有新文件,我们将带回一个 304 响应头,没有新文件,但整个往返延迟。 如果我们重新验证导致许多 304的文件 ,那么可能会增加数百毫秒的不必要开销。

immutable 是一种告诉浏览器文件永远不会改变的方式 - 它是 不可变的 - 因此永远不会打扰重新验证它。 我们可以完全消除往返延迟的开销。 可变或不可变文件的含义是什么?

  • style.css :当我们更改此文件的内容时,我们根本不更改其名称。 该文件始终存在,其内容始终更改。 这个文件是可变的。
  • style.ae3f66.css :此文件是唯一的 - 它根据其内容以指纹命名,因此在内容更改的那一刻,我们将获得一个全新的文件。 这个文件是不可变的。

我们将在Cache Busting 部分中更详细地讨论这个问题。

如果我们能够以某种方式向浏览器传达我们的文件是不可变的 - 它的内容永远不会改变 - 那么我们也可以让浏览器知道它不需要检查更新版本:永远不会有更新鲜的版本作为文件当内容发生变化时,它就会停止存在。

这正是 immutable 指令的作用:

  1. Cache-Control: max-age=31536000, immutable

在支持的浏览器中使用 immutable ,用户刷新永远不会在31,536,000秒的缓存生命周期内导致向服务器重新验证。 这意味着没有不必要的往返花费在 304 响应上,这可能会在关键路径上节省大量的延迟( CSS阻止渲染 )。 在高延迟连接上,这种节省可能是有形的。

注意: 您不应该应用于 immutable 任何不可变的文件。 您还应该有一个非常强大的缓存清除策略,这样您就不会无意中缓慢地缓存 immutable 已应用 的文件 。


stale-while-revalidate

我真的,真的希望有更好的支持 stale-while-revalidate

到目前为止,我们已经谈了很多关于重新验证的内容:浏览器返回服务器以检查是否有更新的文件的过程。 在高延迟连接上,单独重新验证的持续时间可能是显而易见的,并且该时间只是死时间 - 直到我们从服务器听到,我们既不能释放缓存副本( 304 )也不能下载新文件( 200 )。

什么 stale-while-revalidate 提供的是在浏览器允许使用过时的缓存的同时,我们正在检查新版本的宽限期(由美国定义)。

  1. Cache-Control: max-age=31536000, stale-while-revalidate=86400

这告诉浏览器,“这个文件可以使用一年,但在那一年结束后,您还有一天时间可以继续为这个过时的资源提供服务,同时在后台向服务器重新验证它。”。

stale-while-revalidate 对于非关键资源来说,这是一个很好的指令,当然,我们想要最新的版本,但我们知道如果我们在检查更新时再次使用过时的响应,就不会造成任何损害。


stale-if-error

以类似的方式 stale-while-revalidatestale-if-error 允许浏览器在宽限期内,如果重新验证的资源返回 5xx -class错误 ,它可以允许返回陈旧的响应 。

  1. Cache-Control: max-age=2419200, stale-if-error=86400

在这里,我们指示缓存该文件28天(2,419,200秒),如果我们在那之后遇到错误,我们允许额外的一天(86,400秒),在此期间我们将允许过时的缓存文件。


no-transform

no-transform 与存储,提供或重新验证最新文件没有任何关系,但它确实指示中间件不能修改或 转换 任何响应。

中间件可能修改响应的一个常见场景是代表开发人员对用户进行优化:电信公司提供商可能通过其堆栈代理图像请求,并在通过移动连接将其传递给最终用户之前对其进行优化。

这里的问题是,开发人员开始失去对其资源展示的控制,电信公司所做的图像优化可能被认为过于激进和不可接受,或者我们可能已经将图像优化到了理想的程度,任何进一步的优化都是不必要的。

在这里,我们想要指示这个中间件不要转换我们的任何内容。

  1. Cache-Control: no-transform

no-transform头可以与任何其他指令一起,并且不需要其他指令来让它自己运行。

NB 一些转换是一个好主意:CDN为需要前者或可以使用后者的用户选择Gzip或Brotli编码; 图像转换服务自动转换为WebP; 等等

注意 如果您正在运行HTTPS(您应该这样做),那么中间件和代理无论如何都无法修改有效负载,因此 no-transform 无效。

使缓存失效

谈论缓存而不谈论缓存失效是不负责任的。我总是建议在考虑缓存策略之前先解决缓存占用策略。

缓存失效解决了这个问题:我只是告诉浏览器明年使用这个文件,但我只是更改了它,我不希望用户等一整年再得到新的副本!我如何干预?!

没有缓存崩溃 - style.css

这是最不受欢迎的事情:绝对没有任何缓存失效。 这是一个可变文件,我们真的很难缓存半身像。

你应该非常谨慎地缓存这些文件,因为一旦它们在用户的设备上,我们几乎失去了对它们的所有控制权。

尽管这个例子是一个样式表,但HTML页面正好落入这个阵营。 我们无法更改网页的文件名 - 想象一下会造成的破坏! - 这正是我们根本不会缓存它们的原因。

请求参数 - style.css?v=1.2.14

在这里,我们仍然有一个可变的文件,但是我们在它的文件路径中添加了一个查询字符串。虽然比什么都不做要好,但它仍然不完美。如果有任何东西要除去这个查询字符串,我们将回到前一类,即完全没有缓存总线。许多代理服务器和cdn都不会缓存任何带有查询字符串的内容,无论是通过配置(例如,来自CloudFlare自己的文档:……请求“style.css”?当从缓存中提供服务时,“某些内容”将被规范化为“style.css”),或者进行防御(查询字符串可能包含特定于某个特定响应的信息)。

指纹 - style.ae3f66.css

指纹识别是迄今为止缓存文件的首选方法。 通过在每次内容更改时逐字更改文件,我们在技术上不会缓存任何内容:我们最终得到一个全新的文件! 这是非常强大的,并允许使用 immutable 。 如果您可以在静态资产上实现此功能,请执行此操作! 一旦您设法实现这种非常可靠的缓存清除策略,您就可以使用最具侵略性的缓存形式:

  1. Cache-Control: max-age=31536000, immutable

实施细节

这种方法的关键是文件名的变化,但它不 具备 成为一个指纹。 以下所有示例都具有相同的效果:

  1. /assets/style.ae3f66.css :改变文件内容的哈希值。
  2. /assets/style.1.2.14.css :发布版本变化。
  3. /assets/1.2.14/style.css :通过更改URL中的目录来必变。

但是,最后一个示例 意味着 我们对每个版本进行版本控制而不是每个单独的文件。 这反过来意味着如果我们只需要缓存我们的样式表,我们还必须缓存该版本的所有静态文件。 这可能是浪费,所以更喜欢选项(1)或(2)。

Clear-Site-Data

使缓存失效是很难实现的— famously so—所以目前有一个规范正在进行中,它可以帮助开发人员在一瞬间彻底清除整个缓存: Clear-Site-Data

我不想在这篇文章中详细介绍,因为 Clear-Site-Data 它不是一个 Cache-Control 指令,但实际上是一个全新的HTTP头。

  1. Clear-Site-Data: "cache"

将此http header头应用于您的任何一个资源将清除整个网站的缓存,而不仅仅是它所附加的文件。 这意味着,如果您需要从所有访问者的缓存中强制清除整个站点,则可以将上述标题应用于HTML有效内容。

在撰写本文时, 浏览器支持 仅限于Chrome,Android Webview,Firefox和Opera。

提示: 有许多指令,它们的 Clear-Site-Data 将接受: "cookies""storage""executionContexts" ,和 "*" (其中,自然意味着“所有上述的”)。


例子和场景

好的,我们来看看一些场景以及 Cache-Control 我们可能采用 的 headaer头 类型 。

网上银行

类似于在线银行应用程序页面列出您最近的交易,您当前的余额以及可能敏感的银行帐户详细信息的内容需要是最新的(想象一下,这个页面列出了一周前出现的余额!)和也保持非常私密(您不希望您的银行详细信息存储在共享缓存(或任何缓存,真的))。

为此,让我们一起来:

  1. Request URL: /account/
  2. Cache-Control: no-store

根据规范,这足以阻止浏览器在私有和共享缓存中保持对磁盘的响应:

no-store 响应指令指示高速缓存必须不存储任一立即请求或响应的任何部分。 该指令适用于私有和共享缓存。 在此上下文中“不得存储”意味着缓存不得故意将信息存储在非易失性存储中,并且必须尽力尝试在转发后尽快从易失性存储中删除信息。

但如果你想要非常防守,也许你可以选择:

  1. Request URL: /account/
  2. Cache-Control: private, no-cache, no-store

这将明确指示不在公共缓存(例如CDN)中存储任何内容,以始终提供最新鲜的副本,而不是将任何内容保存到存储中。

直播列车时刻表

如果我们正在构建一个显示近乎实时信息的页面,我们希望保证用户始终能够看到我们可以提供的最新的信息(如果该信息存在)。 我们来使用:

  1. Request URL: /live-updates/
  2. Cache-Control: no-cache

这个简单的指令意味着浏览器不会直接使用缓存,需要去询问服务器是否有更新内容。这意味着用户永远不会显示过时的列车信息,但如果服务器指示缓存是最新信息,则直接从缓存中获取文件。

这通常是几乎所有网页的合理默认值:给我们提供最新的内容,但如果可能的话,让我们使用缓存。

常见问题页面

像FAQ这样的页面可能很少更新,其中的内容不太可能是时间敏感的。 它肯定不像实时运动得分或飞行状态那么重要。 我们可能会暂时缓存这样的HTML页面,并强制浏览器定期检查新内容,而不是每次访问。 我们这样做:

  1. Request URL: /faqs/
  2. Cache-Control: max-age=604800, must-revalidate

这告诉浏览器将HTML页面缓存一周(604,800秒),并且一周结束后,我们需要检查服务器是否有更新。

注意: 对于同一网站中的不同页面具有不同的缓存策略可能会导致您的 no-cache 主页请求 style.f4fa2b.css 其引用 的最新版本 的问题 ,但您的三天缓存的常见问题解答页面仍然指向 style.ae3f66.css 。 这种影响可能很小,但这是你应该注意的一个场景。

静态JS(或CSS)应用程序包

让我们说我们的 app.[fingerprint].js 更新很频繁 - 可能与我们每次发布的版本有关 - 但是我们也会在每次更改时指导文件(好工作!)然后我们可以做这样的事情:

  1. Request URL: /static/app.1be87a.js
  2. Cache-Control: max-age=31536000, immutable

我们经常更新我们的JS并不重要:因为我们能够可靠地缓存它,我们可以根据需要缓存它。 在这种情况下,我们选择将其缓存一年。 我选择了一年,因为首先,一年是很长一段时间,但其次,浏览器实际上不太可能持有这么久的文件(浏览器的存储空间有限,可用于HTTP缓存,所以他们自己定期清空部分内容;用户可以清除自己的缓存)。 超过一年的任何事情可能都不会有效。

此外,由于此文件的内容永远不会更改,我们可以向浏览器发出此文件不可变的信号。 即使用户刷新页面,我们也不需要重新验证它全年。 我们不仅可以获得使用缓存的速度优势,还可以避免重新验证的延迟损失。

装饰图像

想象一下伴随文章的纯装饰照片。 它不是信息图表或图表,它不包含任何对理解页面其余部分至关重要的内容,并且用户甚至都不会注意到它是否完全丢失。

图像通常是一个重要的资产下载,所以我们想要缓存它; 它对页面并不重要,因此我们不需要获取最新版本; 我们可能甚至可以在图像过时之后提供服务。 我们开工吧:

  1. Request URL: /content/masthead.jpg
  2. Cache-Control: max-age=2419200, must-revalidate, stale-while-revalidate=86400

这里我们告诉浏览器将图像存储28天(2,419,200秒),我们要在28天的时间限制后与服务器核对更新,如果图像少于一天(86,400秒)过时了,让我们在后台获取最新版本时使用那个。


要记住的关键事项

  • 缓存失效非常重要。 在开始执行缓存策略之前,请制定缓存清除策略。
  • 一般来说,缓存HTML内容是一个坏主意。 HTML网址不能被破坏,并且由于您的HTML页面通常是页面其余部分子资源的入口点,因此您最终也会缓存对静态资源的引用。 这将使您(和您的用户)感到沮丧。
  • 如果要缓存任何HTML,在站点上的不同类型的HTML页面上使用不同的缓存策略可能会导致不一致,如果一类页面总是新鲜的,而其他页面有时从缓存中获取。
  • 如果你可以可靠地缓存(带有指纹)你的静态资产,那么你也可以一次性全押并多次缓存多年的 immutable 指令以获得良好的衡量标准。
  • 非关键内容可以使用类似指令的陈旧宽限期 stale-while-revalidate
  • immutable 并且 stale-while-revalidate 不仅为我们提供了缓存的传统优势,而且还允许我们在重新验证时降低延迟成本。

尽可能避免使用网络可以为我们的用户带来更快的体验(并且我们的基础架构的吞吐量也大大降低)。 通过充分了解我们的资产,并概述我们可以使用的资产,我们可以开始设计针对我们自己的应用程序的非常精细,定制和有效的缓存策略。

缓存规则一切。


资源及相关阅读

© 本文著作权归原作者所有 翻译自:csswizardry 阅读原文(英文)