【问题标题】:Serve index file instead of download prompt提供索引文件而不是下载提示
【发布时间】:2021-06-30 15:03:15
【问题描述】:

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

example.com/directory example.com/directory/

末尾带有/ 的那个错误地提示浏览器下载一个带有随机散列的零字节文件作为文件名。没有斜线它会返回我的 404 页面。

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

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

更新(根据 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 错误路由。

【问题讨论】:

标签: html amazon-web-services amazon-s3 aws-lambda amazon-cloudfront


【解决方案1】:

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

网站端点和 REST 端点都有numerous differences,包括这个。

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

如果由于某种原因您需要使用 REST 端点(例如您不想公开存储桶),那么您需要 CloudFront 中的两个 Lambda@Edge 触发器,以相当接近地模拟此功能。

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

Origin Response 触发器可以在将响应写入 CloudFront 缓存之前检查并可能修改响应。源响应触发器还可以检查生成响应的请求之前的原始请求。我们使用它来检查响应是否是错误的。如果是,并且原始请求确实 not 似乎是针对索引文档或文件(具体来说,在路径中的最后一个斜杠之后,“文件”至少有一个字符,后跟一个点,后跟至少一个字符——如果是这样,那可能是一个“文件”)。如果两者都不是,我们将重定向到原始路径加上我们附加的最终/

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

以下是用 Node.js 8.10 编写的 Lambda@Edge 函数。这个 Lambda 函数会根据上下文修改其行为,使其表现为源请求或源响应。在 Lambda 中发布版本后,将该版本的 ARN 与 CloudFront 缓存行为设置关联为源请求和源响应触发器。

'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 成熟为 Web CDN。我还有其他需求,例如更高级的路由(example.com/users/username,其中用户名作为变量传递......等等)。我想我要试试你的 Lambda@Edge 函数,非常感谢!!!
  • 这行得通。我还测试了一个使用 Lambda 函数进行身份验证的 CloudFront 发行版,它可以正常工作。在修复之前,对于网站子文件夹/test-folder/。 Microsoft Edge 创建名为 test-folder * 的空下载文件,Chrome 创建名为 download * 的空文件。这个答案解决了这个问题。
【解决方案2】:

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

  1. 使用默认(关闭)权限创建全新的 S3 存储桶,或从目标存储桶中删除所有公共访问权限。
  2. 禁用静态网站托管。你不需要它。
  3. 如果您还没有,请将您的 SSL 证书获取到 Amazon,以便您可以将其附加到将指向您的 S3 存储桶的云端分发。
  4. 使用证书创建指向目标 S3 存储桶的云端分发。
  5. 对于源配置,使用 www.yourdomain.com.s3.amazonaws.com 表单作为源,而不是静态网站托管 URL(无论如何都应该禁用)。
  6. 让云端配置自动更改 S3 存储桶访问(“限制存储桶访问”)。您希望仅访问仅限于此云端分发的存储桶(通过特定身份)。没有人应该直接访问您的 S3 存储桶,特别是因为它可以通过 http(没有“s”)提供服务。
  7. 在云端“常规”选项卡下(或在设置过程中)将您的默认根对象设置为“index.html”或其他。否则,对https://www.yourdomain.com/ 的请求将显示权限被拒绝。

【讨论】:

  • 这是一个更好的解决方案,因为它避免了对 S3 存储桶的公开访问。
  • 常规标签不再存在。在页面中搜索“默认根对象”
  • 这对子文件夹不起作用
【解决方案3】:

AWS 最近推出了CloudFront Functions,可用于此用例。 与 Lambda@Edge 相比,CloudFront 函数更便宜、更快、更易于实施和测试

下面是一个示例函数,如果在访问路径时未提供 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找到

【讨论】:

    【解决方案4】:

    这种类型的行为通常由您的 HTTP(s) 标头数据控制/引起,具体而言,您的客户端收到的 Content-Type

    Inspect the header 并尝试调整从您的服务器返回的内容。这应该会导致您的解决方案。

    1. 在 Chrome 中,访问一个 URL,右键单击,选择 Inspect 以打开开发者工具。
    2. 选择网络选项卡。
    3. 重新加载页面,在左侧面板中选择任意一个 HTTP 请求,右侧面板会显示 HTTP 标头。

    【讨论】:

    • 好主意!我有一个脚本可以复制我所有的没有 .html 扩展名的 html 文件,并将它们添加到 S3 中,并将内容类型设置为 HTML。这可能是问题的根源。让我们找出...
    • 好的,没有帮助:(
    猜你喜欢
    • 1970-01-01
    • 2011-04-11
    • 2012-01-18
    • 1970-01-01
    • 2014-06-12
    • 2016-03-08
    • 1970-01-01
    • 2017-07-28
    • 2010-12-20
    相关资源
    最近更新 更多