如何为Cloudfront上的静态托管网站的子目录设置默认根对象?

wye*_*r33 77 amazon-s3 amazon-web-services amazon-cloudfront

如何在Cloudfront上的静态托管网站上为子目录设置默认根对象?具体来说,我想www.example.com/subdir/index.html在用户要求时提供服务www.example.com/subdir.注意,这是用于提供在S3存储桶中保存的静态网站.此外,我想使用原始访问标识将对S3存储桶的访问限制为仅限Cloudfront.

现在,我知道的Cloudfront作品不同于S3和Amazon规定明确:

CloudFront默认根对象的行为与Amazon S3索引文档的行为不同.将Amazon S3存储桶配置为网站并指定索引文档时,即使用户请求存储桶中的子目录,Amazon S3也会返回索引文档.(索引文档的副本必须出现在每个子目录中.)有关将Amazon S3存储桶配置为网站和索引文档的更多信息,请参阅Amazon Simple Storage Service开发人员指南中的Amazon S3上的主机网站章节.

因此,即使Cloudfront允许我们指定默认的根对象,这只适用于www.example.com而不适用于www.example.com/subdir.为了解决这个难题,我们可以将原始域名更改为指向S3给出的网站端点.这很好用,可以统一指定根对象.不幸的是,这似乎与原始访问标识不相容.具体来说,以上链接指出:

切换到编辑模式:

Web分布 - 单击"起源"选项卡,单击要编辑的原点,然后单击"编辑".您只能为Origin Type为S3 Origin的原点创建原始访问标识.

基本上,为了设置正确的默认根对象,我们使用S3网站端点而不是网站存储桶本身.这与使用原始访问标识不兼容.因此,我的问题归结为两者

  1. 是否可以为Cloudfront上的静态托管网站的所有子目录指定默认根对象?

  2. 是否可以为从Cloudfront提供的内容设置原始访问标识,其中源是S3网站端点而不是S3存储桶?

JBa*_*zuk 201

IS办法做到这一点.而不是通过在下拉列表中选择它(www.example.com.s3.amazonaws.com)将其指向您的桶,将其指向您的桶的静态域(例如www.example.com.s3- website-us -west-2.amazonaws.com):

在此输入图像描述

感谢此AWS论坛主题

  • 任何人都知道,如果有s3来源与网络来源,这是否收费不同? (5认同)
  • 这与 Cloud Front - Origin Access Identity 不兼容。您将无法以这种方式限制对 S3 存储桶的访问。 (4认同)
  • 如果我只想通过"HTTPS"服务我的整个网站和文件,这项工作是否正常? (3认同)
  • OP明确表示这种方法对他不起作用:“为了解决此难题,我们可以更改原始域名以指向S3给出的网站终结点。这很好用,并且可以统一指定根对象。不幸的是, ,这似乎与原始访问身份不兼容”。AWS本身似乎为此建议lamda @ edge-https://aws.amazon.com/blogs/compute/implementing-default-directory-indexes-in-amazon-s3-backed-amazon-cloudfront-origins-using- lambdaedge / (3认同)
  • 问题是如果您的原始路径不是/且用户忘记了最后一个/,例如/ blog vs // blog /,那么S3将重定向到`[原始路径] / blog /` (2认同)
  • 这是否意味着必须将S3作为Web服务器启用? (2认同)
  • 你让我不必编写一些愚蠢的 lambda 函数来完成世界上所有其他静态 Web 服务器多年来一直在做的事情。谢谢。 (2认同)

ktu*_*nik 48

(2021 年 5 月新功能)CloudFront 函数

下面创建一个简单的 JavaScript 函数

function handler(event) {
    var request = event.request;
    var uri = request.uri;
    
    // Check whether the URI is missing a file name.
    if (uri.endsWith('/')) {
        request.uri += 'index.html';
    } 
    // Check whether the URI is missing a file extension.
    else if (!uri.includes('.')) {
        request.uri += '/index.html';
    }

    return request;
}
Run Code Online (Sandbox Code Playgroud)

在这里阅读更多信息

  • 这非常有效,而且比使用 Lambda 便宜得多。[这里有一个示例](https://gist.github.com/mikebroberts/09e8c8b4aaac6e26149c4622fd492414),说明如何在无服务器框架部署脚本中设置 CF 函数(只需将函数代码替换为上面答案中的代码/链接)。 (2认同)

Aid*_*din 18

我完全同意这是一个荒谬的问题!事实上,CloudFront 知道index.html充当默认根对象,但他们仍然说它不适用于子目录(),这是完全奇怪的!

CloudFront 默认根对象的行为与 Amazon S3 索引文档的行为不同。当您将 Amazon S3 存储桶配置为网站并指定索引文档时,即使用户请求存储桶中的子目录,Amazon S3 也会返回索引文档。

我个人认为,AWS 已经做到了这一点,因此 CloudFront 仅成为 CDN(加载资产,其中没有任何逻辑),并且对网站中路径的每个请求都应由“服务器”(例如 EC2 节点)提供服务/Php 服务器,或 Lambda 函数。)

这种限制的存在是否是为了增强安全性,或者将事物分开(即逻辑和存储分开),或者赚更多的钱(强制人们拥有专用服务器,即使是静态内容)还有待争论。


无论如何,我在这里总结了可能的解决方案及其优缺点。

1) S3 可以是公共的 - 使用自定义源。

这是最简单的一个,最初由 @JBaczuk答案以及此 github gist发布。由于 S3 已经支持index.html通过静态网站托管在子目录中提供服务,因此您需要做的就是:

  1. 转到S3,启用静态网站托管
  2. 抓取以下形式的 URLhttp://<bucket-name>.s3-website-us-west-2.amazonaws.com
  3. 在 CloudFront 中创建一个新的源并将其输入为自定义源(而不是 S3 ORIGIN),以便 CloudFront 在获取内容时将其视为外部网站。

优点:

  1. 非常容易设置。
  2. 它支持/about//about、 和/about/index.html并将最后两个正确重定向到第一个。

缺点:

  1. 如果您在 S3 存储桶中的文件不在 S3 的根目录中(例如,/artifacts/*然后转到www.domain.com/about(不带尾随/)会将您重定向到www.domain.com/artifacts/about您根本不想要的内容!基本上,如果您提供服务,S3 中的重定向/about会中断/about/来自 CloudFront 的路径和文件路径(从根目录)不匹配。

  2. 安全性和功能性:您无法将 S3 设为私有。显然,这是因为 CloudFront 的Origin Access Identity将不受支持,因为 CloudFront 被指示将此 Origin 作为随机网站。这意味着用户可以直接从 S3 获取文件,由于安全/WAF 问题,这可能不是您所期望的,并且如果您的 JS/html 仅依赖于您的域的路径,则网站实际上可以正常工作。

  3. [也许是一个问题] CloudFront 和 S3 之间的通信不是建议优化内容的方式。

  4. [也许?]有人抱怨说它对于发行版中的多个起源(即想要/blog去某个地方)不能顺利工作

  5. [也许?]有人抱怨它没有按预期保留原始查询参数。

2)官方解决方案——使用Lambda函数。

这是官方解决方案(尽管该文档是 2017 年的)。还有一个准备启动的第 3 方应用程序github 中的 JavaScript 源代码)和示例 Python Lambda 函数(此答案)。

从技术上讲,通过这样做,您可以创建一个迷你服务器(他们称之为无服务器!),仅向 CloudFront 的 S3 源请求提供服务(因此,它基本上位于 CloudFront 和 S3 之间。)

优点:

  1. 嘿,这是官方解决方案,因此可能持续时间更长,并且是最优化的解决方案。
  2. 如果您愿意并可以控制它,您可以自定义 Lambda 函数。您可以在其中支持进一步重定向。
  3. 如果正确实现,(就像第 3 方 JS 之一,我不认为是官方的)它支持/about/两者/about(从后者重定向而不跟踪/到前者)。

缺点:

  1. 这是另一件事需要设置。
  2. 有眼睛又是一回事,所以不会坏掉。
  3. 当出现问题时要检查的又是一件事。
  4. 还有一件事需要维护——例如,这里的第三方自 2021 年 1 月以来一直开放 PR(现在是 2021 年 4 月)。
  5. 第 3 方 JS 解决方案不保留查询参数。/about?foo=bar301 重定向到/about/NOT也是如此/about/?foo=bar。您需要对该 lambda 函数进行更改才能使其正常工作。
  6. 第 3 方 JS 解决方案保留/about/为规范版本。如果您想/about成为规范版本(即其他格式通过 301 重定向到它),您必须对脚本进行更改。
  7. [次要]仅适用于 us-east-1(自 2020 年以来在 Github 上开放问题,2021 年 4 月仍然开放,并且是一个实际问题)。
  8. [次要]它有自己的成本,尽管考虑到 CloudFront 的缓存,应该不会很大。

3) 在 S3 中创建假“文件夹文件” - 使用手动脚本。

它是前两者之间的解决方案——它支持 OAI(私有 S3)并且不需要服务器。虽然有点恶心!

您在这里要做的是,运行一个脚本,为/about/index.html它的每个子目录在 S3 中创建一个名为 (has keyof)的对象/about,并将该 HTML 文件(内容和content-type)复制到该对象中。

示例脚本可以在此 Reddit 答案和使用 AWS CLI 的答案中找到。

优点:

  1. 安全:支持 S3 Private 和 CloudFront OAI。
  2. 无需额外的实时片段:脚本运行预上传到 S3(或一次性),然后系统仅通过 S3 和 CF 两片保持完整。

缺点:

  1. [需要确认]我相信它支持/about但不/about/支持尾随/
  2. 从技术上讲,您存储了两个不同的文件。如果有大量 HTML 文件,可能看起来很混乱,并使您的部署成本高昂。
  3. 您的脚本必须手动查找所有子目录并在 S3 中创建一个虚拟对象。这有可能在未来被打破。

附言。其他技巧)

在自定义错误上使用 Javascript 的肮脏伎俩

虽然它看起来不像真实的东西,但这个答案值得一些赞扬,IMO!

您让拒绝访问(404 变成 403)通过,然后捕获它们,并通过 JS 手动将它们重定向到正确的位置。

优点

  1. 再次强调,易于设置。

缺点

  1. 它依赖于客户端的 JavaScript。
  2. 它会扰乱 SEO——尤其是当爬虫不运行 JS 时。
  3. 它会扰乱用户的浏览器历史记录。(即后退按钮)并且可能可以通过 HTML5 进行改进(并变得更加复杂!)history.replace


ken*_*ske 11

激活S3托管意味着你必须向全世界打开桶.就我而言,我需要将存储桶保密,并使用原始访问标识功能来限制对Cloudfront的访问.像@Juissi建议的那样,Lambda函数可以修复重定向:

'use strict';

/**
 * Redirects URLs to default document. Examples:
 *
 * /blog            -> /blog/index.html
 * /blog/july/      -> /blog/july/index.html
 * /blog/header.png -> /blog/header.png
 *
 */

let defaultDocument = 'index.html';

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

    if(request.uri != "/") {
        let paths = request.uri.split('/');
        let lastPath = paths[paths.length - 1];
        let isFile = lastPath.split('.').length > 1;

        if(!isFile) {
            if(lastPath != "") {
                request.uri += "/";
            }

            request.uri += defaultDocument;
        }

        console.log(request.uri);
    }

    callback(null, request);
};
Run Code Online (Sandbox Code Playgroud)

发布功能后,请转至AWS控制台中的cloudfront分发.转到Behaviors,然后选择Origin RequestLambda Function Associations,最后将ARN粘贴到新功能.

  • 有一个准备好部署的lambda函数类似于该函数:https://serverlessrepo.aws.amazon.com/applications/arn:aws:serverlessrepo:us-east-1:621073008195:applications~standard-redirects-for-cloudfront (2认同)

Joh*_*ter 7

还有另一种方法可以在子目录中提供默认文件,例如example.com/subdir/.实际上,您可以(以编程方式)存储带有密钥的文件subdir/.此文件不会显示在S3管理控制台中,但它实际存在,CloudFront将为其提供服务.


Max*_*tov 6

AWS 博客发布了一份“官方”指南,建议设置由您的 CloudFront 分配触发的 Lambda@Edge 函数:

当然,期望用户总是在每个 URL 的末尾键入 index.html(甚至知道它应该在那里)是一种糟糕的用户体验。到目前为止,还没有一种简单的方法可以通过 CloudFront 向用户提供这些更简单的 URL(相当于 Apache Web 服务器配置中的 DirectoryIndex 指令)。如果您仍然希望能够使用 OAI 限制对 S3 源的访问,则不会。但是,随着 Lambda@Edge 的发布,您可以使用在 CloudFront 边缘节点上运行的 JavaScript 函数来查找这些模式并从 S3 源请求适当的对象键。

解决方案

在此示例中,您使用 CloudFront 边缘的计算能力检查来自客户端的请求。然后重新编写请求,以便 CloudFront 为以“/”结尾的任何请求 URI 请求默认索引对象(在本例中为 index.html)。

当对 Web 服务器发出请求时,客户端指定要在请求中获取的对象。您可以使用此 URI 并对其应用正则表达式,以便在 CloudFront 从源请求对象之前将这些 URI 解析为默认索引对象。使用以下代码:

'use strict';
exports.handler = (event, context, callback) => {

    // Extract the request from the CloudFront event that is sent to Lambda@Edge
    var request = event.Records[0].cf.request;

    // Extract the URI from the request
    var olduri = request.uri;

    // Match any '/' that occurs at the end of a URI. Replace it with a default index
    var newuri = olduri.replace(/\/$/, '\/index.html');

    // Log the URI as received by CloudFront and the new URI to be used to fetch from origin
    console.log("Old URI: " + olduri);
    console.log("New URI: " + newuri);

    // Replace the received URI with the URI that includes the index page
    request.uri = newuri;

    // Return to CloudFront
    return callback(null, request);

};
Run Code Online (Sandbox Code Playgroud)

按照上面链接的指南查看设置此所需的所有步骤,包括 S3 存储桶、CloudFront 分配和Lambda@Edge函数创建。


run*_*wuf 5

aws CLI 一条命令解决方案

如果您不想使用 labmda、javascript 或其他 s3 配置...正如 johan-gorter 和 jeremie 所指出的,index.html可以将其存储为带有 key 的对象subdir/aws我验证了这种方法的有效性,并且似乎是使用CLI实现此目的的最简单方法s3api copy-object

aws s3api copy-object --copy-source bucket_name/subdir/index.html --key subdir/ --bucket bucket_name
Run Code Online (Sandbox Code Playgroud)


Jos*_*ick 3

更新:看来我错了!请参阅 JBaczuk 的答案,这应该是该线程上接受的答案。

不幸的是,你的两个问题的答案是否定的。

1. 是否可以为 Cloudfront 上静态托管网站的所有子目录指定默认根对象?

否。如AWS CloudFront 文档中所述...

...如果您定义默认根对象,则最终用户对发行版子目录的请求不会返回默认根对象。例如,假设index.html是您的默认根对象,并且 CloudFront 收到最终用户对 CloudFront 分配下的安装目录的请求:

http://d111111abcdef8.cloudfront.net/install/

即使有副本,CloudFront 也不会返回默认根对象index.html即使安装目录中出现 的

...

CloudFront 默认根对象的行为与 Amazon S3 索引文档的行为不同。当您将 Amazon S3 存储桶配置为网站并指定索引文档时,即使用户请求存储桶中的子目录,Amazon S3 也会返回索引文档。(索引文档的副本必须出现在每个子目录中。)

2. 是否可以为 Cloudfront 提供的内容设置源访问身份,其中源是 S3 网站端点而不是 S3 存储桶?

不直接。CloudFront 源的选项是 S3 存储桶或您自己的服务器。

不过,第二种选择确实带来了一些有趣的可能性。这可能会违背您想要做的事情的目的,但您可以设置自己的服务器,其唯一的工作就是成为 CloudFront 源服务器。

当请求传入http://d111111abcdef8.cloudfront.net/install/时,CloudFront 会将此请求转发到您的源服务器,请求/install. 您可以根据需要配置源服务器,包括index.html在这种情况下提供服务。

或者您可以编写一个小型 Web 应用程序,只接受此调用并直接从 S3 获取它。

但我意识到,建立自己的服务器并担心扩展它可能会违背您最初尝试做的事情的目的。