如何在Cloudfront上的静态托管网站上为子目录设置默认根对象?具体来说,只要用户要求www.example.com/subdir/index.html
,我就会www.example.com/subdir
提供服务。注意,这是用于提供在S3存储桶中保存的静态网站。此外,我想使用原始访问标识将对S3存储桶的访问限制为仅限Cloudfront。
现在,我知道Cloudfront的工作方式与S3和亚马逊的工作方式不同specifically:
CloudFront默认根对象的行为与 Amazon S3索引文档的行为。配置Amazon S3时 作为网站并指定索引文档,Amazon S3返回 索引文档,即使用户请求了一个子目录 桶。 (索引文件的副本必须出现在每个 子目录。)有关配置Amazon S3的更多信息 作为网站和索引文档的存储桶,请参阅主机 亚马逊简单存储服务中Amazon S3上的网站 开发者指南。
因此,尽管Cloudfront允许我们指定默认的根对象,但这仅适用于www.example.com
而不适用于www.example.com/subdir
。为了解决这个难题,我们可以将原始域名更改为指向S3给出的网站端点。这很好用,可以统一指定根对象。不幸的是,这似乎与origin access identities不相容。具体而言,上述链接指出:
更改为编辑模式:
Web分布 - 单击“起源”选项卡,单击要编辑的原点,然后单击“编辑”。您只能创建原始访问权限 原产地类型为S3原产地的原产地标识。
基本上,为了设置正确的默认根对象,我们使用S3网站端点而不是网站存储桶本身。这与使用原始访问标识不兼容。因此,我的问题归结为
是否可以为Cloudfront上的静态托管网站的所有子目录指定默认根对象?
是否可以为从Cloudfront提供的内容设置原始访问标识,其中源是S3网站端点而不是S3存储桶?
答案 0 :(得分:183)
IS 是一种方法。不要通过在下拉列表中选择它(www.example.com.s3.amazonaws.com)将其指向您的桶,而是将其指向您的桶的静态域(例如www.example.com.s3- website-us -west-2.amazonaws.com):
答案 1 :(得分:8)
激活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);
};
发布功能后,请转到AWS控制台中的cloudfront分发。转到Behaviors
,然后选择Origin Request
下的Lambda Function Associations
,最后将ARN粘贴到新功能中。
答案 2 :(得分:4)
还有另一种方法可以在子目录中提供默认文件,例如example.com/subdir/
。实际上,您可以(以编程方式)存储桶中密钥为subdir/
的文件。此文件将不显示在S3管理控制台中,但它实际存在,CloudFront将为其提供服务。
答案 3 :(得分:3)
该问题的解决方法是使用lambda @ edge重写请求。只需要为CloudFront分发的查看者请求事件设置lambda,并重写以' /'结尾的所有内容。 AND不等于' /'使用默认根文档,例如的index.html。
答案 4 :(得分:2)
更新:看起来我错了!请参阅JBaczuk的答案,该答案应该是此主题的接受答案。
不幸的是,你的两个问题的答案都是否定的。
<强> 1。是否可以为Cloudfront上的静态托管网站的所有子目录指定默认根对象?
没有。正如AWS CloudFront docs ...
中所述...如果定义默认根对象,则对分发的子目录的最终用户请求不会返回默认根对象。例如,假设
index.html
是您的默认根对象,CloudFront会在您的CloudFront分配下收到安装目录的最终用户请求:http://d111111abcdef8.cloudfront.net/install/
即使安装目录中出现
index.html
的副本,CloudFront也不会返回默认的根对象。...
CloudFront默认根对象的行为与Amazon S3索引文档的行为不同。将Amazon S3存储桶配置为网站并指定索引文档时,即使用户请求存储桶中的子目录,Amazon S3也会返回索引文档。 (索引文档的副本必须出现在每个子目录中。)
<强> 2。是否可以为从Cloudfront提供的内容设置原始访问标识,其中源是S3网站端点而不是S3存储桶?
不直接。您对CloudFront的起源选项是S3存储桶或您自己的服务器。
这是第二种选择确实开辟了一些有趣的可能性。这可能会破坏您尝试做的目的,但您可以设置自己的服务器,其唯一的工作是成为CloudFront原始服务器。
当http://d111111abcdef8.cloudfront.net/install/的请求进入时,CloudFront会将此请求转发到您的原始服务器,并要求/install
。您可以根据需要配置源服务器,包括在这种情况下提供index.html
。
或者您可以编写一个小型网络应用程序,只需接听此电话并直接从S3获取。
但我意识到,设置自己的服务器并担心缩放它可能会破坏你首先尝试做的目的。
答案 5 :(得分:2)
Johan Gorter 和 Jeremie 表示 index.html
可以存储为键为 subdir/
的对象。
我通过awscli
的{{1}}
s3api copy-object
答案 6 :(得分:2)
我完全同意这是一个荒谬的问题! CloudFront 知道将 index.html
服务为 Default Root Object 并且他们仍然说它不适用于子目录 (source) 的事实非常奇怪!
CloudFront 默认根对象的行为与 Amazon S3 索引文档的行为不同。当您将 Amazon S3 存储桶配置为网站并指定索引文档时,即使用户请求存储桶中的子目录,Amazon S3 也会返回索引文档。
我个人认为,AWS 已经做到了这一点,因此 CloudFront 仅成为 CDN(加载资产,其中没有任何逻辑)并且对您网站中的路径的每个请求都应该从“服务器”(例如 EC2 Node/Php 服务器,或 Lambda 函数。)
这种限制的存在是为了增强安全性,还是为了让事物分开(即逻辑和存储分离),还是为了赚更多钱(强制人们拥有专用服务器,即使是静态内容),还有待商榷。
无论如何,我在这里总结了可能的解决方案解决方法,以及它们的优缺点。
这是最简单的一个,最初由@JBaczuk answer 以及 this github gist 发布。由于 S3 已经支持通过静态网站托管在子目录中提供 index.html
,您需要做的就是:
http://<bucket-name>.s3-website-us-west-2.amazonaws.com
的形式抓取网址/about/
、/about
和 /about/index.html
,并正确地将最后两个重定向到第一个。如果您在 S3 存储桶中的文件不在 S3 的根目录中(例如在 /artifacts/*
中,那么转到 www.domain.com/about
(没有尾随 /
)会将您重定向到www.domain.com/artifacts/about
这是您根本不想要的东西!如果您从 CloudFront 提供服务,并且文件的路径(从根)不提供,则 S3 中的 /about
到 /about/
重定向基本上会中断t 匹配。
安全性和功能性:您不能将 S3 设为私有。这是因为 CloudFront 的 Origin Access Identity 将不受支持,很明显,因为 CloudFront 被指示将此 Origin 作为随机网站。这意味着用户可能会直接从 S3 获取文件,由于安全/WAF 问题,这可能不是您所需要的,如果您的 JS/html 仅依赖于您的域的路径,则网站实际运行.
[可能是一个问题] CloudFront 和 S3 之间的通信不是推荐的优化内容的方式。
[也许吗?] 有人抱怨分发版中的多个 Origin 无法顺利运行(即希望 /blog
去某个地方)
[也许?] 有人抱怨它没有按预期保留原始查询参数。
它是 the official solution(尽管文档是 2017 年的)。还有一个 ready-to-launch 3rd-party Application (JavaScript source in github) 和示例 Python Lambda 函数 (this answer)。
从技术上讲,通过这样做,您可以创建一个迷你服务器(他们称之为无服务器!),它只向 S3 提供 CloudFront 的源请求(因此,它基本上位于 CloudFront 和 S3 之间。)
/about/
和 /about
(后者的重定向没有尾随 {{ 1}} 到前者)。/
是 301 重定向到 /about?foo=bar
而不是 /about/
。您需要对该 lambda 函数进行更改才能使其正常工作。/about/?foo=bar
保留为规范版本。如果您希望 /about/
成为规范版本(即其他格式通过 301 重定向到它),您必须对脚本进行更改。这是前两者之间的解决方案——它支持 OAI(私有 S3)并且不需要服务器。不过有点恶心!
您在这里所做的是,您运行一个脚本,该脚本为 /about
的每个子目录在 S3 中创建一个名为(具有 /about/index.html
of)key
的对象并复制该 HTML 文件(将内容和 /about
) 放入此对象中。
可以使用 AWS CLI 在 this Reddit answer 和 this answer 中找到示例脚本。
content-type
但不支持带有尾随 /about
的 /about/
。虽然它看起来不像真的,但 this answer 值得称赞,IMO!
您让拒绝访问(404 变为 403)通过,然后捕获它们,并通过 JS 手动将它们重定向到正确的位置。
/
改进(并变得更复杂!)。答案 7 :(得分:1)
我知道这是一个老问题,但我自己也在努力解决这个问题。最终,我的目标不是在目录中设置默认文件,而是更多地使得文件的最终结果是在其末尾没有.html
的情况下提供
我最终从文件名中删除了.html
,并以编程方式/手动将mime类型设置为text/html
。它不是传统的方式,但它似乎确实有效,并且在不牺牲cloudformation的好处的情况下满足了我对漂亮网址的要求。设置mime类型很烦人,但在我看来支付这个好处的代价很小
答案 8 :(得分:1)
有一个"official" guide published on AWS blog建议设置由您的CloudFront发行版触发的Lambda @ Edge函数:
当然,期望用户总是在每个URL的末尾键入index.html(或者甚至知道它应该在该URL中)是一种糟糕的用户体验。到目前为止,还没有一种通过CloudFront向用户提供这些更简单的URL(相当于Apache Web Server配置中的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);
};
请按照上面链接的指南查看进行此设置所需的所有步骤,包括S3存储桶,CloudFront发行版和Lambda@Edge函数创建。
答案 9 :(得分:1)
使用lambda @ edge的另一种替代方法是使用CloudFront的错误页面。设置Custom Error Response以将所有403发送到特定文件。然后将javascript添加到该文件,以将index.html附加到以/结尾的网址。示例代码:
if ((window.location.href.endsWith("/") && !window.location.href.endsWith(".com/"))) {
window.location.href = window.location.href + "index.html";
}
else {
document.write("<Your 403 error message here>");
}
答案 10 :(得分:0)
@ johan-gorter上面指出,CloudFront提供以/结尾的密钥文件 经过调查,看来该选项有效,并且可以以编程方式在S3中创建这种类型的文件。因此,我写了一个小lambda,它在S3上创建文件时触发,后缀为index.html或index.htm
它的作用是将对象dir/subdir/index.html
复制到对象dir/subdir/
import json
import boto3
s3_client = boto3.client("s3")
def lambda_handler(event, context):
for f in event['Records']:
bucket_name = f['s3']['bucket']['name']
key_name = f['s3']['object']['key']
source_object = {'Bucket': bucket_name, 'Key': key_name}
file_key_name = False
if key_name[-10:].lower() == "index.html" and key_name.lower() != "index.html":
file_key_name = key_name[0:-10]
elif key_name[-9:].lower() == "index.htm" and key_name.lower() != "index.htm":
file_key_name = key_name[0:-9]
if file_key_name:
s3_client.copy_object(CopySource=source_object, Bucket=bucket_name, Key=file_key_name)
答案 11 :(得分:0)
可以使用新发布的cloudfront functions,这里是sample code。
注意:如果您使用的是 static website hosting,那么您不需要任何函数!