如何使用 S3/Cloudfront 在子文件夹中提供 index.html?

jco*_*lum 7 amazon-s3 amazon-web-services static-site amazon-cloudfront

有一个叫做 的桶www.foo.site。在该站点中,有一个登录页面、一个关于页面和一些bar/*文件夹中的一些页面。每个bar/*都有一个 index.html 页面:bar/a/index.html、bar/b/index.html 等。

登陆页面运行良好(意味着 www.foo.site 将在我浏览时加载)但是当我点击我的关于链接等时/about/index.html页面和/bar/index.html页面没有得到服务。如果curl我得到 404 的 URL。我'我试过分别设置源路径和源域名:

第一次尝试:

domain name: www.foo.site.s3.amazonaws.com
origin path: (blank)
Run Code Online (Sandbox Code Playgroud)

第二次尝试:

domain name: s3-us-west-1.amazonaws.com
origin path: www.foo.site
Run Code Online (Sandbox Code Playgroud)

两者的默认文档都是 index.html。

没有一个工作。上面提到的所有 S3 页面都可以直接浏览。含义https://s3-us-west-1.amazonaws.com/www.foo.site/bar/index.html加载预期的 html。

这一定是我缺少的一些 Cloudfront 设置。可能是我的 DNS 记录中的某些内容?是否可以通过 Cloudfront 在 S3“文件夹”中提供 html 文件?

jar*_*mod 9

这里有一些资源在通过隐式方式从 S3 提供 index.html 时很有帮助,https://domain/folder/而不必显式使用https://domain/folder/index.html

配置 CloudFront 分配时的关键是:

不要为您的 CloudFront 分配配置默认根对象

如果您将 index.html 配置为默认根对象,https://domain/则将正确提供服务,https://domain/index.html但不会使用诸如此类的子文件夹引用https://domain/folder/

在将 CF 分配连接到 S3 存储桶时,不要使用 Cloudfront 中的下拉菜单也很重要。您需要使用 S3 静态站点的 URL。

  • 第一个链接不起作用。请注意,如果想采用这种方式,则必须将存储桶公开,因为原始访问身份不适用。 (2认同)
  • 不应用默认根对象并将发行版连接到静态站点 URL 解决了该问题。 (2认同)

cap*_*ack 9

您现在可以使用 CloudFront Functions 来执行此操作,而不是使用 Lambda@Edge。正如AWS 新闻博客中所述,使用 CF 函数有很多好处。

您可以使用此 CloudFormation 代码段来部署 S3/CloudFront/Functions 资源。

Resources:
  DistributionBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketEncryption:
        ServerSideEncryptionConfiguration:
        - ServerSideEncryptionByDefault:
            SSEAlgorithm: AES256
      BucketName: test-site-site
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      Tags:
      - Key: STAGE
        Value: dev
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain

  DistributionBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref DistributionBucket
      PolicyDocument:
        Statement:
        - Action: s3:*
          Condition:
            Bool:
              aws:SecureTransport: 'false'
          Effect: Deny
          Principal:
            AWS: "*"
          Resource:
            - !GetAtt DistributionBucket.Arn
            - !Sub '${DistributionBucket.Arn}/*'
        - Action: s3:GetObject
          Effect: Allow
          Principal:
            CanonicalUser: !GetAtt DistributionOAI.S3CanonicalUserId
          Resource:
            - !Sub '${DistributionBucket.Arn}/*'
        Version: '2012-10-17'

  DistributionCertificate:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref DomainName
      DomainValidationOptions:
      - DomainName: !Ref DomainName
        HostedZoneId: !Ref HostedZoneId
      Tags:
      - Key: STAGE
        Value: dev
      ValidationMethod: DNS

  DistributionOAI:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: Identity for s3-origin-test-site

  Distribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Aliases:
          - !Ref DomainName
        Comment: Distribution for test-site
        DefaultCacheBehavior:
          AllowedMethods:
            - GET
            - HEAD
          CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6
          Compress: true
          FunctionAssociations:
            - EventType: viewer-request
              FunctionARN: !GetAtt DistributionFunction.FunctionMetadata.FunctionARN
          TargetOriginId: s3-origin-test-site
          ViewerProtocolPolicy: redirect-to-https
        DefaultRootObject: index.html
        Enabled: true
        HttpVersion: http2
        IPV6Enabled: true
        Origins:
        - DomainName: !GetAtt DistributionBucket.RegionalDomainName
          Id: s3-origin-test-site
          S3OriginConfig:
            OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/${DistributionOAI}'
        PriceClass: PriceClass_200
        ViewerCertificate:
          AcmCertificateArn:
            Ref: DistributionCertificate
          MinimumProtocolVersion: TLSv1.2_2021
          SslSupportMethod: sni-only
      Tags:
      - Key: STAGE
        Value: dev

  DistributionFunction:
    Type: AWS::CloudFront::Function
    Properties:
      AutoPublish: true
      FunctionCode: |
        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;
        }
      FunctionConfig:
        Comment: Redirect-Default-Index-Request
        Runtime: cloudfront-js-1.0
      Name: test-site-site-redirect-index-request

  DistributionRecordSetA:
    Type: AWS::Route53::RecordSet
    Properties:
      Name: !Sub '${DomainName}.'
      Type: A
      AliasTarget:
        DNSName: !GetAtt Distribution.DomainName
        HostedZoneId: Z2FDTNDATAQYW2
      Comment: 'Alias record for test-site'
      HostedZoneId: !Ref HostedZoneId

  DistributionRecordSetAAAA:
    Type: AWS::Route53::RecordSet
    Properties:
      Name: !Sub '${DomainName}.'
      Type: AAAA
      AliasTarget:
        DNSName: !GetAtt Distribution.DomainName
        HostedZoneId: Z2FDTNDATAQYW2
      Comment: 'Alias record for test-site'
      HostedZoneId: !Ref HostedZoneId
Run Code Online (Sandbox Code Playgroud)

  • 这就像一个魅力,并且很好地没有导致“/index.html”显示在浏览器 URL 中。而且,与已接受的答案不同,这与 Cloudfront 上的 HTTPS 兼容!谢谢你! (4认同)

Ask*_*ker 7

提炼@captainblack的优秀答案,您只需将以下CloudFront函数与您的CloudFront发行版的缓存行为的查看者请求相关联即可实现您的目标:

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)

这比 @jarmod 的答案中过时的方法要好得多,因为这更容易实现,并且与那些方法不同,它通过 HTTPS 与 CloudFront 兼容。

此方法的另一个好处是,它会隐藏/index.html浏览器 URL 末尾的丑陋字符串,除非用户键入该字符串。

  • 很有魅力! (2认同)