【问题标题】:Lambda script to direct to fallback S3 domain subfolder when not found找不到时指向后备 S3 域子文件夹的 Lambda 脚本
【发布时间】:2020-01-05 23:37:25
【问题描述】:

根据 questionone 以下代码,我可以将 S3 存储桶中的子文件夹指向我的域。

但是,在未找到子域的情况下,我会收到以下错误消息:

<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>2CE9B7837081C817</RequestId>
<HostId>
T3p7mzSYztPhXetUu7GHPiCFN6l6mllZgry+qJWYs+GFOKMjScMmRNUpBQdeqtDcPMN3qSYU/Fk=
</HostId>
</Error>

我不希望它显示此错误消息,相反,在这种情况下,我希望从另一个 S3 存储桶子域(即example-bucket.s3-website.us-east-2.amazonaws.com/error)提供服务,例如,用户将收到一条奇特的错误消息。因此,在找不到 S3 存储桶子文件夹的情况下,它应该退回到那里。我如何通过更改下面的节点函数来实现这一点。

'use strict';

// if the end of incoming Host header matches this string, 
// strip this part and prepend the remaining characters onto the request path,
// along with a new leading slash (otherwise, the request will be handled
// with an unmodified path, at the root of the bucket)

const remove_suffix = '.example.com';

// provide the correct origin hostname here so that we send the correct 
// Host header to the S3 website endpoint

const origin_hostname = 'example-bucket.s3-website.us-east-2.amazonaws.com'; // see comments, below

exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    const headers = request.headers;
    const host_header = headers.host[0].value;

    if(host_header.endsWith(remove_suffix))
    {
        // prepend '/' + the subdomain onto the existing request path ("uri")
        request.uri = '/' + host_header.substring(0,host_header.length - remove_suffix.length) + request.uri;
    }

    // fix the host header so that S3 understands the request
    headers.host[0].value = origin_hostname;

    // return control to CloudFront with the modified request
    return callback(null,request);
};

【问题讨论】:

    标签: amazon-s3


    【解决方案1】:

    Lambda@Edge 函数是一个源请求触发器——它在检查 CloudFront 缓存并发生缓存未命中之后运行,紧接在请求之前(因为它在被触发代码)被发送到源服务器。当响应从源端到达时,此代码已完成,不能用于修改响应。

    有几种解决方案,包括一些在概念上有效但效率极低的解决方案。尽管如此,为了彻底起见,我还是会提到这些以及更清洁/更好的解决方案。

    Lambda@Edge 有4 possible trigger points:

    • viewer-request - 当请求第一次到达 CloudFront 时,在检查缓存之前;每次请求都会触发。
    • origin-request - 在请求被确认为缓存未命中之后,但在请求被发送到源服务器之前;仅在缓存未命中时触发。
    • origin-response - 在从源服务器返回响应(无论是成功还是错误)之后,但在响应可能存储在缓存中并返回给查看器之前;如果此触发器修改了响应,则修改后的响应将存储在 CloudFront 缓存中(如果可缓存),并返回给查看器;仅在缓存未命中时触发
    • viewer-response - 在响应返回给查看器之前,无论是来自源还是缓存;为每个非错误响应触发,除非该响应是由查看器请求触发器自发发出的,或者是自定义错误文档将状态代码设置为 200 的结果(明确的反模式,但仍然可能),或是 CloudFront 生成的 HTTP 到 HTTPS 的重定向。

    任何触发点都可以控制信号流generate its own spontaneous response,从而改变 CloudFront 通常会做的事情——例如如果您直接从源请求触发器生成响应,CloudFront 实际上不会联系源......所以您可以理论上做的是检查源请求触发器中的 S3 以查看请求将成功并生成自定义错误响应。 AWS Javascript SDK 自动捆绑到 Lambda@Edge 环境中。从技术上讲,这在几乎任何情况下都可能是一个糟糕的想法,因为对 S3 的额外“前瞻”请求会增加成本和延迟。

    另一种选择是编写单独的源响应触发器来检查错误,如果发生错误,请将其替换为来自触发器代码的自定义响应。但是这个想法也被认为是不可行的,因为对于缓存未命中的所有响应都会触发该触发器,无论是成功还是失败,这会增加成本和延迟,在大多数情况下会浪费时间。

    更好的想法(成本、性能、易用性)是CloudFront Custom Error Pages,它允许您定义一个特定的 HTML 文档,CloudFront 将使用该文档来处理与指定代码匹配的每个错误(例如,403 表示访问被拒绝,如在原始问题中)。 CloudFront 还可以在处理这些错误时将该 403 更改为 404。这要求您在错误文件的来源是存储桶时做几件事:

    • 创建第二个指向存储桶的 CloudFront 源
    • 创建一个新的缓存行为,将错误文件的一个路径(例如/shared/errors/not-found.html)精确路由到新的原点(这意味着您不能在任何子域上使用该路径——它总是会去任何时候被请求直接到错误文件)
    • 为代码 403 配置 CloudFront 自定义错误响应以使用路径 /shared/errors/not-found.html
    • 至少在测试时将错误缓存最小 TTL 设置为 0,以避免给自己带来一些挫败感。请参阅my write-up on this feature,但忽略我所说的部分“将Customize Error Response 设置为No

    但是...这可能需要也可能不需要,因为 S3 的网络托管功能还包括可选的Custom Error Document 支持。您需要在原始存储桶中创建一个 HTML 文件,在存储桶上启用网站托管功能,并将 CloudFront 源域名更改为存储桶的网站托管终端节点,该终端节点位于 S3 控制台中,但采用以下形式的${bucket}.s3-website.${region}.amazonaws.com。在某些地区,由于遗留原因,主机名可能在s3-website 之后有一个破折号-,而不是一个点.,但点格式应该适用于任何地区。


    我几乎犹豫要提到另一个想到的选项,因为它相当先进,我担心描述可能看起来很复杂......但您也可以执行以下操作,它会非常巧妙,因为它允许您可能会为每个请求的错误 URL 生成一个自定义 HTML 页面。

    创建一个 CloudFront Origin Group,将您的主存储桶作为主要存储桶,将第二个空的“占位符”存储桶作为辅助存储桶。第二个存储桶的唯一目的是让我们为 CloudFront 提供一个它计划连接的有效名称,即使我们实际上不会连接到它,正如下面可能会清楚的那样。

    当对主要来源的请求失败时,匹配配置的错误状态代码之一,将联系次要来源。这是为了处理源失败时的情况,但我们可以将其用于我们的目的,因为在实际联系故障转移源之前,同源请求触发器会再次触发。

    如果主要来源返回您为故障转移配置的 HTTP 状态代码,则当 CloudFront 将请求重新路由到第二个来源时,Lambda 函数会再次触发。

    https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/high_availability_origin_failover.html#concept_origin_groups.lambda

    (更准确的说法是“......当 CloudFront 准备将请求重新路由到第二个来源时”,因为触发器首先触发。)

    当触发器第二次触发时,不会保留触发的具体原因,但有 a way to identify whether you're running in the first or second invocation:这两个值之一将包含 CloudFront 准备联系的源服务器的主机名:

    event.Records[0].cf.request.origin.s3.domainName     # S3 rest endpoints
    event.Records[0].cf.request.origin.custom.domainName # non-S3 origins and S3 website-hosting endpoints
    

    因此我们可以在触发代码中测试适当的值(取决于源类型),寻找第二个“占位符”存储桶的名称。如果存在,绕过当前逻辑并从 Lambda 函数内部生成 404 响应。这可以是动态/自定义的 HTML,例如页面 URI,也可能是根据是否请求 / 或其他页面而变化的 HTML。如上所述,自发地从源请求触发器生成响应会阻止 CloudFronr 实际联系源。从源请求触发器生成的响应被限制为 1MB,但这对于这个用例来说应该是不够的。

    【讨论】:

    • 嗨,迈克尔。非常感谢您的详细回复。我决定走Cloudfront Custom Error Pages 路线,但我不明白以下步骤:create a new cache behavior that routes exactly that one path (e.g. /shared/errors/not-found.html) to the error file over to the new origin (this means you can't use that path on any of the subdomains -- it will always go directly to the error file any time it's requested)。你能更详细地向我解释一下吗?我在这里创建了第二个原点:imgur.com/a/A10e1KI(一个带有 _errors 原点 ID)。
    • 我将缓存行为设置如下,imgur.com/a/bjWqElY 但它将所有“正确”的子域链接重定向到错误页面。我不确定在哪里配置缓存行为以执行其他操作。
    • 您已将新缓存行为设置为匹配路径模式* - 所有请求,但新缓存行为的路径模式应该只是错误文件的路径,例如/shared/errors/not-found.html。此外,除非错误文件位于不同的存储桶中,否则您不需要单独的缓存行为,并且您不应在新源上设置 Origin Path。那不会做你想要的。 (CloudFront 在将每个请求发送到源之前,将 Origin Path 的值添加到请求 URI 的开头。仅当对源的所有请求都需要在路径前附加一个静态字符串时才有用。)
    • 嗨迈克尔,很抱歉再次打扰,但我遇到了一个相关问题:stackoverflow.com/questions/59986729/…。如果可能的话,会感谢你的建议吗?谢谢
    猜你喜欢
    • 1970-01-01
    • 2012-07-26
    • 2019-02-05
    • 2013-11-22
    • 2016-12-19
    • 2014-06-19
    • 1970-01-01
    • 2021-02-06
    • 2015-10-15
    相关资源
    最近更新 更多