在同一 URL 下在 S3 + Cloudfront 上托管多个 SPA 网络应用程序

Hosting multiple SPA web apps on S3 + Cloudfront under same URL

我有两个静态 Web 应用程序 (create-react-apps),它们目前位于两个单独的 S3 存储桶中。两个存储桶都配置为 public 读取 + 静态网络托管,访问他们的 S3 托管 URLs 正确显示网站。

Bucket 1 - First App:
   index.html
   static/js/main.js

Bucket 2 - Second App:
   /secondapp/
       index.html
       static/js/main.js

我为此设置了一个 Cloudfront - 默认的 cloudfront 源正确加载 FirstApp,这样 www.mywebsite.com 默认加载 index.html。

对于 SecondApp,我设置了缓存行为,以便路径模式 secondapp/* 指向 SecondApp 存储桶 URL。

在我的浏览器中,当我访问 www.mywebsite.com/secondapp/ 时,它会正确显示第二个网络应用程序。

但是,如果我省略尾部的斜线,我反而会看到第一个应用程序,这是不需要的。 如果我访问 www.mywebsite.com/secondapp/something,我也会看到第一个应用程序,这也是不受欢迎的。 (我希望它加载 secondapp 的 .html)

两个应用程序都配置为通过 react-router-dom.dom.

使用 html5 推送状态

我想要的行为是访问以下显示正确的 site/bucket:

www.mywebsite.com - 目前工作

www.mywebsite.com/secondapp/ - 目前工作

www.mywebsite.com/secondapp -(没有尾部斜杠)不工作,显示第一个应用程序

www.mywebsite.com/secondapp/something_else - 不工作,显示第一个应用程序

我怎样才能达到预期的效果?

谢谢!

在研究了这个问题之后,我能够使用 lambda@edge (https://aws.amazon.com/lambda/edge/)

解决它

通过部署一个简单的 javascript 函数将特定路径路由到所需的 s3 存储桶,我们能够实现类似 nginx 的路由设置。 该函数位于我们的 Cloudfront CDN 上的 lambda@edge 上,这意味着您可以指定 何时 它被触发。对我们来说,它在 "Origin Request"

我的设置如下:

  • 我使用了一个 s3 存储桶,并将我的第二个应用程序部署在一个子文件夹中 "second-app"
  • 我在 "U.S. East N Virginia" 上创建了一个新的 Lambda 函数。该区域在这里很重要,因为您只能在该区域托管 lambda 函数 @edge。
  • 请参阅下面的实际 Lambda 函数
  • 创建后,转到您的 CloudFront 配置并转到 "Behaviors > Select the Default (*) path pattern and hit Edit"
  • 滚动到底部"Lambda Function Associations"
  • Select "Origin Request" 形成下拉列表
  • 输入您的 lambda 函数的地址 (arn:aws:lambda:us-east-1:12345667890:function:my-function-name)

这是我使用的 lambda 函数的示例。


var path = require('path');

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;

  const parsedPath = path.parse(request.uri);

  // If there is no extension present, attempt to rewrite url
  if (parsedPath.ext === '') {
    // 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(/second-app.*/, 'second-app/index.html');

    // Replace the received URI with the URI that includes the index page
    request.uri = newuri;
  }
  // If an extension was not present, we are trying to load static access, so allow the request to proceed
  // Return to CloudFront
  return callback(null, request);
};

这些是我用于此解决方案的资源: