如何使用不同的策略在 Cloudfront 上缓存 API

How to cache an API on Cloudfront with different strategies

我有多个端点,一些是相对于给定用户的,而另一些是全局的。

我想缓存所有这些,但我正在为该策略而苦苦挣扎。

GET /me

应该缓存但每个用户不同。所以我可以使用 Authorization header.

中提供的 URI + 访问令牌的组合进行缓存
GET /products/1

应该缓存并且对每个人都一样。但它也需要 Authorization header 才能被访问。所以我只能缓存 URI。

如何在独特的 CloudFront 分配上实施此行为?好像只能有一个缓存组合。

谢谢,

CloudFront 可以根据不同的路径模式在多个缓存键组合上进行缓存。每个 缓存行为 都有一个特定的 路径模式 它将匹配 - 就像 /products/* - 然后有一个默认的缓存行为* 匹配任何其他内容。 (这个是默认创建的,不能删除)。 CloudFront 分配最多支持 25 个独特的路径模式,每个。 AWS Support 可能会增加该限制,但由于每个路径模式都支持 * 以及 ? 通配符,并且有一个默认值可以捕获其他所有内容,这可能就足够了。

CloudFront 的工作基于基本假设——除了有限的例外——转发到源的任何内容都可能导致源改变响应。因此,默认情况下,几乎所有内容都从原始请求中删除。

例如User-Agent,在请求发送到源站之前设置为Amazon CloudFront。为什么?因为如果允许 User-Agent 通过,源端可能会根据对 user-agent 字符串的分析修改内容,例如识别设备类型(例如台式机,移动设备,平板电脑,smart-tv) 并做出相应的回应。 CloudFront 无法知道 before-hand 原始服务器将如何处理这些值。但是,如果您需要 CloudFront 假设更改 user-agent 可能会更改响应,CloudFront 还需要为它看到的每个 user-agent 字符串缓存每个 object 的唯一副本,并且仅使用这些缓存的副本来匹配另一个相同的请求。您可以将 User-Agent header 列入白名单以转发到源,这就是发生的情况:CloudFront 然后将 header 与每个请求一起发送,并将 User-Agent 添加到 cache key -- 这是事物的 collection,始终包括请求路径,并且始终包括 CloudFront 可用于唯一标识任何未来请求的白名单 header它应该被认为是真正相同的。¹

Cookie 和查询字符串参数也会导致源服务器修改其响应,因此默认情况下这些也会从请求中删除。您可以指定哪些 cookie,或所有 cookie,或 none(默认)。您可以指定哪些查询字符串参数,或所有查询字符串参数,或 none(默认)。无论您指定什么,都会添加到缓存键,并转发到源,CloudFront 只会提供与 entire 缓存键完全匹配的缓存响应。

Authorization header 是一个特别有趣的例子,因为您似乎发现了一个问题,但可能忽略了另一个非常重要的问题。

GET /me 的情况下 -- 每个提交请求的唯一用户(由 Authorization 标识)都会得到不同的响应。路径 /me 的缓存行为设置需要将 Authorization header 列入白名单。很简单。

但是 GET /products/1 呢?这里是龙。您 仍然 必须将 Authorization header 转发到源,否则 CloudFront 实际上不知道它是否是有效的授权请求。尽管直觉表明可以使用缓存的响应,因为每个授权用户都应该收到相同的响应...... CloudFront 不能这样做,因为你需要源来验证是否可以对特定的响应做出有利的响应 Authorization header。它必须发送到源,这意味着它必须是缓存键的一部分。每个唯一的 和有效 Authorization header 值都会导致 CloudFront 获取并缓存我们希望重用的响应的新副本。它实际上只会在完全相同的用户再次请求它时被重用,并且具有相同的 Authorization header.

但是,对于我们需要根据请求的某些属性 authenticate/authorize 请求的情况,CloudFront 有一个潜在的解决方案,但我们不想通过转发 Authorization headers 或 cookie 到源,从而将它们添加到缓存键中。

Lambda@Edge 是一项 CloudFront 增强功能,允许您在 CloudFront 信号流的 4 个战略点拦截、检查并可能修改请求和响应——在请求端,检查缓存之前和之后,并且在响应端,在缓存被写入之前和最终响应(无论是命中还是未命中)被 returned 到查看器之前。 HTTP 请求 and/or 响应转换为 JavaScript 数据结构,您的自定义 Node.js "trigger" 代码可以修改 CloudFront 的行为。

在您的情况下,Lambda@Edge 查看器请求 触发器似乎是一种解决方案。

查看器请求触发器可以访问原始请求,包括 CloudFront 将剥离的 headers 和 cookie 以及查询参数,因为它们不是缓存键的一部分。

在这里,对于/products/*,您将逻辑嵌入到触发功能代码中以验证e Authorization header。您将触发函数分配给 /products/* 缓存行为。

如果 Authorization 有效,您让请求通过并 return 控制到 CloudFront,如果可用,它将从缓存中提供,否则从您的原始服务器请求 - 但没有Authorization header 存在,因为对于这些路径,您不转发它,所以它不在缓存键中。³您的响应现在可缓存和可重用。

如果 Authorization header 无效,您直接在触发器代码中生成拒绝响应,CloudFront return 将您的响应发送给未经授权的请求者,不进行缓存检查 object.

但是如何从触发器函数中验证 Authorization header?这取决于您的平台如何运作。如果是 JWT,您可以直接在函数代码中验证它。但是 Lambda@Edge 环境可以访问 Internet,与 CloudFront 目前正在处理的请求分开,因此一种选择可能是直接向您的服务器发出 HTTP 请求。另一个可能涉及您发送到 DynamoDB 等服务的查找。这是高度特定于实现的。

Lambda@Edge 在可重复使用的容器中运行 运行。虽然不能保证重用,但观察表明会发生大量重用,因此将 Authorization header 查找的结果缓存在全局内存中 object 可以预期具有很高的命中率。

当然,权衡是该解决方案的成本、复杂性和增加的延迟是否超过了潜在的高缓存命中率可能导致的较低资源使用和减少延迟的好处。


¹ 那里有太多可能的 user-agent 值,User-Agent 通常是转发到源(并因此包含在缓存键中)的一个非常糟糕的选择,所以 CloudFront 团队针对此特定案例提出了解决方案。 CloudFront 可以为您分析 user-agent,并将浏览器分类为台式机、移动设备 non-tablet、移动平板电脑或智能电视,而不是白名单 User-Agent,您可以将以下一项或多项列入白名单CloudFront-Is-*-Viewer headers...导致能够根据正在使用的浏览器的一般 class 提供响应,并显着提高缓存命中率,如果 User-Agent 转发。这些浏览器分类 header 不是 user-agent 字符串成为缓存键的一部分,而是成为缓存键的一部分,结果只有 4 个唯一组合。 (理论上,由于每个值都是布尔值,假设 all-false 是不可能的,有 15 种可能的组合,但我从未遇到过超过 4 种。)

² 查询字符串参数还有一个选项,即 "forward all, cache based on whitelist." 这是发送到源的所有内容都是缓存键的一部分的一般规则的例外。类似的选项不适用于请求 headers 或 cookie。

³ 那么你怎么知道请求是你的来源应该处理的,因为它不会有 Authorization header?您在 CloudFront 中注入一个自定义来源 header,其中包含一个秘密的静态密钥,只有您的来源和 CloudFront 知道,建立信任。