CloudFront 可以根据不同的路径模式缓存在多个缓存键组合上。每个缓存行为都有一个特定的路径模式,它会匹配——比如/products/*——然后*有一个默认的缓存行为可以匹配其他任何东西。 (这个是默认创建的,不能删除)。 CloudFront 分配最多支持 25 个唯一路径模式,每个模式。有可能让 AWS Support 提高该限制,但由于每个路径模式都支持 * 和 ? 通配符,并且有一个默认值可以捕获其他所有内容,这可能就足够了。
CloudFront 的工作基于基本假设 - 除了有限的例外 - 转发到源的任何内容都可能导致源改变响应。因此,默认情况下,几乎所有内容都从原始请求中剥离。
例如,User-Agent 在请求发送到源之前设置为 Amazon CloudFront。为什么?因为如果User-Agent 被允许通过,源可能会根据用户代理字符串的分析修改内容,例如识别设备类型(例如台式机、移动设备、平板电脑、智能电视)并做出相应响应. CloudFront 无法事先知道原始服务器将如何处理这些值。但是,如果您需要 CloudFront 假设更改用户代理可能会更改响应,CloudFront 还需要为它看到的每个用户代理字符串缓存每个对象的唯一副本,并仅使用这些缓存副本来匹配另一个相同的请求。您可以将 User-Agent 标头列入白名单以转发到源,这就是发生的情况:CloudFront 然后将该标头与每个请求一起发送,并将 User-Agent 添加到 缓存键 - 这是事物的集合,始终包括请求路径,并且始终包括 CloudFront 可用于唯一标识它应视为真正相同的任何未来请求的白名单标头。¹
Cookie 和查询字符串参数也会导致源服务器修改其响应,因此默认情况下,这些参数也会从请求中删除。您可以指定哪些 cookie、所有 cookie 或无(默认)。您可以指定哪些查询字符串参数,或所有查询字符串参数,或无(默认)。您指定的任何内容都会添加到缓存键,并转发到源,CloudFront 只会提供与 整个 缓存键完全匹配的缓存响应。
Authorization 标头是一个特别有趣的例子,因为您似乎发现了一个问题,但可能忽略了另一个非常重要的问题。
在GET /me 的情况下——每个提交请求的唯一用户(由Authorization 标识)都会得到不同的响应。路径/me 的缓存行为设置需要将Authorization 标头列入白名单。很简单。
但是GET /products/1 呢?这里是龙。您仍然必须将 Authorization 标头转发到源,因为 CloudFront 否则实际上不知道它是否是有效的授权请求。尽管直觉建议可以使用缓存的响应,因为每个授权用户都应该收到相同的响应...... CloudFront 不能这样做,因为您需要源来验证是否可以对特定的 Authorization 标头做出有利响应.它必须被发送到源,这意味着它必须是缓存键的一部分。每个唯一有效 Authorization 标头值都会导致 CloudFront 获取并缓存我们希望重用的响应的新副本。它实际上只有在完全相同的用户再次请求时才会被重用,并且具有相同的 Authorization 标头。
但是,对于我们需要根据请求的某些属性对请求进行身份验证/授权的情况,CloudFront 有一个潜在的解决方案,但我们不想通过转发 Authorization 标头或 cookie 来稀释缓存命中的可能性到原点,从而将它们添加到缓存键中。
Lambda@Edge 是一项 CloudFront 增强功能,可让您在 CloudFront 信号流中的 4 个战略点拦截、检查并可能修改请求和响应——在请求端,在检查缓存之前和之后,以及在响应端,在缓存被写入之前和最终响应(无论是命中还是未命中)返回给查看器之前。 HTTP 请求和/或响应被转换为 JavaScript 数据结构,您的自定义 Node.js“触发器”代码可以修改 CloudFront 的行为。
在您的情况下,Lambda@Edge 查看器请求触发器似乎是一种解决方案。
查看器请求触发器可以访问原始请求,包括 CloudFront 将剥离的标头、cookie 和查询参数,因为它们不是缓存键的一部分。
就在这里,对于/products/*,您将逻辑嵌入到触发函数代码中以验证Authorization 标头。您将触发函数分配给/products/* 缓存行为。
如果 Authorization 有效,您让请求通过并将控制权返回给 CloudFront,如果可用,它将从缓存中提供服务,否则从您的源服务器请求 - 但不存在 Authorization 标头,因为对于这些路径,您无需转发它,因此它不在缓存键中。³您的响应现在可以缓存和重用。
如果 Authorization 标头无效,您可以直接在触发代码中生成拒绝响应,CloudFront 会将您的响应返回给未经授权的请求者,而不对对象进行缓存检查。
但是如何在触发函数中验证Authorization 标头?这取决于您的平台如何工作。如果是 JWT,可以直接在函数代码中验证。但是 Lambda@Edge 环境可以访问 Internet,与 CloudFront 目前正在处理的请求分开,因此一种选择可能是直接向您的服务器发送 HTTP 请求。另一个可能涉及您发送到 DynamoDB 等服务的查找。这是高度特定于实现的。
Lambda@Edge 函数在可重复使用的容器中运行。虽然不能保证重用,但观察表明会发生大量重用,因此将 Authorization 标头查找的结果缓存在内存中的全局对象中预计会有很高的命中率。
当然,权衡是该解决方案的成本、复杂性和增加的延迟是否超过了降低资源使用和降低潜在高缓存命中率可能导致的延迟的好处。
¹ 有太多可能的用户代理值,User-Agent 通常是转发到源(因此包括在缓存键中)的一个非常糟糕的选择,因此 CloudFront 团队想出了一个解决方案这个具体案例。 CloudFront 可以为您分析用户代理,并将浏览器分类为桌面、移动非平板电脑、移动平板电脑或智能电视,您可以将一个或多个 CloudFront-Is-*-Viewer 标头列入白名单,而不是将 User-Agent 列入白名单。 .. 导致能够根据所使用的一般浏览器类别提供响应,并显着提高缓存命中率,如果转发User-Agent,缓存命中率将非常低。这些浏览器分类标题不是成为缓存键的一部分,而是用户代理字符串成为缓存键的一部分,导致只有 4 个唯一的组合。 (理论上,由于每个值都是布尔值,因此假设不可能全部为假,则有 15 种可能的组合,但我从未遇到过超过 4 种。)
² 查询字符串参数还有一个选项,即“全部转发,基于白名单缓存”。这是一般规则的一个例外,即发送到源的所有内容都是缓存键的一部分。类似的选项不适用于请求标头或 cookie。
³ 那么,既然它没有Authorization 标头,您怎么知道该请求是您的来源应该处理的?您在 CloudFront 中注入一个自定义源标头,其中包含一个只有您的源和 CloudFront 知道的秘密静态密钥,从而建立信任。