提供索引文件而不是下载提示



我的网站托管在S3上,CloudFront作为CDN,我需要这两个URL表现相同,并在目录中提供index.html文件:

example.com/directoryexample.com/directory/

末尾带有/的文件错误地提示浏览器下载一个零字节文件,该文件的名称带有随机哈希。如果没有斜杠,它将返回我的404页。

如何获得两个路径来在目录中传递index.html文件?

如果有办法我"应该"做到这一点,那太好了!这正是我所希望的,但如果不是,我可能会尝试使用Lambda@Edge进行重定向。无论如何,我在其他情况下都需要它,所以关于如何从Lambda@Edge也会有帮助:)

更新(根据John Hanley的评论)

curl -i https://www.example.com/directory/

HTTP/2 200 
content-type: application/x-directory
content-length: 0
date: Sat, 12 Jan 2019 22:07:47 GMT
last-modified: Wed, 31 Jan 2018 00:44:16 GMT
etag: "[id]"
accept-ranges: bytes
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 [id].cloudfront.net (CloudFront)
x-amz-cf-id: [id]

更新

CloudFront有一个行为集,将http转发到https并将请求发送到S3。它在错误选项卡下还有一个404错误路径

S3仅在启用并使用bucket的网站托管功能时提供自动索引文档,方法是指向bucket的站点托管端点${bucket}.s3-website.${region}.amazonaws.com,而不是bucket的通用REST端点${bucket}.s3.amazonaws.com

网站端点和REST端点有许多不同之处,包括这一点。

您看到对象键的这些0字节文件以/结尾的原因是,您正在使用S3控制台或其他实际创建0字节对象的实用程序在bucket中创建文件夹对象。一旦文件夹中有对象,就不需要它们了——但它们是在S3控制台中显示空文件夹的唯一方法,S3控制台将名为foo/的对象显示为名为foo的文件夹,即使没有其他键前缀为foo/的对象。它是控制台中文件夹层次结构的可视化模拟的一部分,即使S3中的对象从未真正"在"文件夹中。

如果出于某种原因需要使用REST端点——比如不想公开bucket——那么您需要两个Lambda@EdgeCloudFront中的触发器,以相当接近地模拟此功能。

Origin Request触发器可以在检查CloudFront缓存之后,在将请求发送到Origin之前,检查和修改请求。我们使用它来检查以/结尾的路径,如果找到了,则附加index.html

Origin Response触发器可以在将响应写入CloudFront缓存之前检查并可能修改响应。Origin Response触发器还可以检查生成响应的请求之前的原始请求。我们使用它来检查响应是否为错误。如果是,并且原始请求看起来不是索引文档或文件(特别是,在路径中的最后一个斜杠之后,"文件"至少有一个字符,后面跟着一个点,后面又跟着至少一个字符——如果是,那可能就是"文件")。如果两者都不是,我们重定向到原始路径加上我们附加的最终/

原始请求和原始响应只在缓存未命中时触发。当缓存命中时,两个触发器都不会触发,因为它们位于CloudFront的原始端,即缓存的背面。可以从缓存提供服务的请求是从缓存提供的,因此不会调用触发器。

以下是Lambda@Edge用Node.js 8.10编写的函数。这一个Lambda函数修改了它的行为,使其行为为原始请求或原始响应,具体取决于上下文。在Lambda中发布版本后,将该版本的ARN与CloudFront Cache Behavior设置关联为Origin Request和Origin Response触发器。

'use strict';
// combination origin-request, origin-response trigger to emulate the S3
// website hosting index document functionality, while using the REST
// endpoint for the bucket
// https://stackoverflow.com/a/54263794/1695906
const INDEX_DOCUMENT = 'index.html'; // do not prepend a slash to this value
const HTTP_REDIRECT_CODE = '302'; // or use 301 or another code if desired
const HTTP_REDIRECT_MESSAGE = 'Found'; 
exports.handler = (event, context, callback) => {
const cf = event.Records[0].cf;
if(cf.config.eventType === 'origin-request')
{
// if path ends with '/' then append INDEX_DOCUMENT before sending to S3
if(cf.request.uri.endsWith('/'))
{
cf.request.uri = cf.request.uri + INDEX_DOCUMENT;
}
// return control to CloudFront, to send request to S3, whether or not
// we modified it; if we did, the modified URI will be requested.
return callback(null, cf.request);
}
else if(cf.config.eventType === 'origin-response')
{
// is the response 403 or 404?  If not, we will return it unchanged.
if(cf.response.status.match(/^40[34]$/))
{
// it's an error.
// we're handling a response, but Lambda@Edge can still see the attributes of the request that generated this response; so, we
// check whether this is a page that should be redirected with a trailing slash appended.  If it doesn't look like an index
// document request, already, and it doesn't end in a slash, and doesn't look like a filename with an extension... we'll try that.
// This is essentially what the S3 web site endpoint does if you hit a nonexistent key, so that the browser requests
// the index with the correct relative path, except that S3 checks whether it will actually work.  We are using heuristics,
// rather than checking the bucket, but checking is an alternative.
if(!cf.request.uri.endsWith('/' + INDEX_DOCUMENT) && // not a failed request for an index document
!cf.request.uri.endsWith('/') && // unlikely, unless this code is modified to pass other things through on the request side
!cf.request.uri.match(/[^/]+.[^/]+$/)) // doesn't look like a filename  with an extension
{
// add the original error to the response headers, for reference/troubleshooting
cf.response.headers['x-redirect-reason'] = [{ key: 'X-Redirect-Reason', value: cf.response.status + ' ' + cf.response.statusDescription }];
// set the redirect code
cf.response.status = HTTP_REDIRECT_CODE;
cf.response.statusDescription = HTTP_REDIRECT_MESSAGE;
// set the Location header with the modified URI
// just append the '/', not the "index.html" -- the next request will trigger
// this function again, and it will be added without appearing in the
// browser's address bar.
cf.response.headers['location'] = [{ key: 'Location', value: cf.request.uri + '/' }];
// not strictly necessary, since browsers don't display it, but remove the response body with the S3 error XML in it
cf.response.body = '';
}
}
// return control to CloudFront, with either the original response, or
// the modified response, if we modified it.
return callback(null, cf.response);
}
else // this is not intended as a viewer-side trigger.  Throw an exception, visible only in the Lambda CloudWatch logs and a 502 to the browser.
{
return callback(`Lambda function is incorrectly configured; triggered on '${cf.config.eventType}' but expected 'origin-request' or 'origin-response'`);
}
};

给出的答案是错误的。Cloudfront有自己的配置,可以让www.yourdomain.com/提供文档。它被称为"默认根对象",其配置位于cloudfront发行版的"常规"选项卡下。以下是获取启用SSL/https的自定义域+cloudfront+s3 bucket的完整步骤。

  1. 创建一个具有默认(关闭)权限的全新S3存储桶,或从目标存储桶中删除所有公共访问权限
  2. 禁用静态网站托管。你不需要它
  3. 如果你还没有,把你的SSL证书拿到亚马逊,这样你就可以把它附加到指向S3存储桶的cloudfront分发版上
  4. 使用cert创建一个指向目标S3 bucket的cloudfront分布
  5. 对于原点配置,请使用www.yourdomain.com.s3.amazonaws.com表单作为原点,而不是静态网站托管URL(无论如何都应该禁用)
  6. 让cloudfront配置自动更改S3 bucket访问("restrict bucket access")。您希望对bucket的访问仅限于此cloudfront分发(通过特定标识)。任何人都不应该直接访问S3存储桶,尤其是因为它可以通过http(没有"s")提供服务
  7. 在cloudfront的"general"选项卡下(或在设置过程中),将默认根对象设置为"index.html"或其他什么。否则,请求https://www.yourdomain.com/将显示权限被拒绝

最近AWS最近推出了可用于此用例的CloudFront函数CloudFront功能相比,更便宜、更快、更易于实现和测试Lambda@Edge

下面是一个示例函数,用于在访问路径时如果没有提供index.html,则将其附加到请求。

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;
}

这将不会在网络浏览器地址栏中附加index.html,这会在浏览时提供更干净的URL。在您的情况下,https://www.example.com/directory/在浏览时将保持原样,但将呈现https://www.example.com/directory/index.html的内容。

更多样本可在https://github.com/aws-samples/amazon-cloudfront-functions/blob/main/url-rewrite-single-page-apps/index.js

这种类型的行为通常由HTTP标头数据控制/引起,特别是客户端接收的内容类型。

检查标题并尝试调整从服务器返回的内容。这应该会带来你的解决方案。

  1. 在Chrome中,访问URL,右键单击,选择"检查"以打开开发工具
  2. 选择"网络"选项卡
  3. 重新加载页面,在左侧面板上选择任何HTTP请求,HTTP标头将显示在右侧面板上

最新更新