S 3 WebアプリとAPIゲートウェイのための単一のCloudfront配布


このポストは、シングルを使う方法を概説しますAmazon CloudFront あなたのウェブアプリの配布は、S 3とあなたのバックエンドAPIでホストされます.

私が達成したい結果は:
  • website.com 私のWebアプリをロードします
  • website.com/non-existent-page 私は人間のフレンドリー404ページを与える
  • website.com/api/* バックエンドAPIにルーティングされます.
  • website.com/api/non-existent-endpoint 私のバックエンドと私の人間に優しい404ページからのマシンフレンドリーなエラー応答を返す
  • 私は使用されますAmazon API GatewayV2 この例のための私のバックエンドとして、原則は他のバックエンドに適用すべきです.

    目次

  • Why
  • Summary
  • Architecture

  • Step by Step Tutorial Using CDK
  • CDK Setup
  • API Gateway with a Lambda Backend
  • S3 Bucket and CloudFront Distribution
  • Add API Gateway as Another CloudFront Origin
  • Lambda@Edge for Handling Redirects
  • Test It Out
  • Key Take Aways
  • Alternatives
  • なぜ


    私が両方のために別々の配布を使うことができるとき、なぜ私のウェブアプリとAPIのために一つのCloudfront配布を使用しますか?例えば、私はちょうど使用することができませんwebsite.com and api.website.com ?
    要するに、サブドメインは管理するのが難しくありえます、そして、若干の組織で、それは新しいサブドメインを要求するのさえ難しいかもしれません.
    サブドメイン管理が困難になる例は、異なる環境です.以下のサブドメインを持つdev環境があるとしましょうdev.website.com ; あなたのAPIはapi.dev.website.com または、dev.api.website.com ? 別のSSL証明書を作成しますか?あなたのコルズのルールは?CSPはどうですか?どのようにあなたのコードベースでこれを管理するつもりですか?
    ...そして、私は永遠に行くことができます.
    それで、一部の人々は、ちょうど使用を考慮するかもしれません/api/ そして、それを一日呼ぶ.このポストはそれらの人々のためです.

    概要


    基本的に、我々は、経路パターンに基づいて複数の起源からCloudfrontサービスを提供します.この場合、私たちは前方にCloudfrontを持ちます/api/* APIゲートウェイへのリクエストと他のすべてのリクエストをs 3に転送します.これだけで結果1、3、4を達成する.
    しかし、誰かがアクセスしようとするなら/non-existent-page あなたは私たちの人間に優しい404エラーではないS 3から“nosuchkey”エラーを取得します.

    CloudFrontカスタムエラー構成を使用して、すべての404エラーをインデックスに上書きする方法について説明します.HTMLまたは専用の404ページ?残念なことに、CloudFrontは、今日の起源ごとのカスタムエラー設定を設定する機能を持っていないので、この場合、バックエンドAPIエラー応答を上書きします(基本的に、ここで解決しようとしている問題のコアです).
    それで、質問のカスタムエラー構成で、我々は残ります.
    ええと、Lambda@edge.

    私たちはLambda@Edge for /non-existent-page あなたのS 3の起源へのルートは、我々のインデックスに戻って404応答をリダイレクト.HTMLまたは専用の404ページに.

    建築


    かなりまっすぐ前方に、私たちはLambda@Edge我々のS 3起源と我々の配布の間で.

    CDKを使用してステップバイステップチュートリアル


    以下は、CDKを使用して作業例を構築するステップバイステップチュートリアルです.
    参考のために、ローカルにインストールしたものです.
    $ node --version
    v14.13.1
    $ yarn --version # you can use npm
    1.22.5
    $ cdk --version
    1.67.0 (build 2b4dd71)
    $ docker --version # used by CDK to compile typescript lambdas
    Docker version 19.03.13, build 4484c46d9d
    
    すべてのソースコードをここで見つけることもできます.

    EVNZ / ブログCFシングル配布


    S 3バケツとAPIゲートウェイバックエンドでホストされるあなたのウェブアプリのための一つのアマゾンCloudfront配布の例


    CDKセットアップ


    タイプスクリプトCDKプロジェクトを作成し、いくつかの依存関係をインストールしましょう.
    $ cdk init app --language typescript
    ...
    $ yarn add \
        @aws-cdk/aws-cloudfront \
        @aws-cdk/aws-apigatewayv2 \
        @aws-cdk/aws-s3 \
        @aws-cdk/aws-s3-deployment \
        @aws-cdk/aws-lambda \
        @aws-cdk/aws-lambda-nodejs \
        @aws-cdk/aws-iam
    ...
    $ yarn add --dev --exact [email protected] # for compiling typescript lambdas
    

    ラムダバックエンドを持つAPIゲートウェイ


    あなたのCDKアプリのlibフォルダの中にbackend とダミーのラムダ関数を設定します(index.ts ) これは200を返します:
    export const handler = async (event: any): Promise<any> => {
      return {
        statusCode: 200,
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ key: "Machine friendly hello world" }),
      };
    };
    
    必要なインポートを追加lib/{your-stack-name}.ts ファイル
    import * as cdk from "@aws-cdk/core";
    import { NodejsFunction } from "@aws-cdk/aws-lambda-nodejs";
    import { Runtime } from "@aws-cdk/aws-lambda";
    import * as apigatewayv2 from "@aws-cdk/aws-apigatewayv2";
    
    ここでは、CDKスタックに次のコードを追加してラムダ関数を作成し、API GateWavv 2と統合できます.
    const httpApi = new apigatewayv2.HttpApi(this, "MyApiGateway");
    
    const helloWorldLambda = new NodejsFunction(this, "HelloWorldLambda", {
      entry: `${__dirname}/backend/index.ts`,
      handler: "handler",
      runtime: Runtime.NODEJS_12_X,
    });
    
    const lambdaIntegration = new apigatewayv2.LambdaProxyIntegration({
      handler: helloWorldLambda,
    });
    
    httpApi.addRoutes({
      path: "/api/helloworld", // You must include the `/api/` since CloudFront will not truncate it
      methods: [apigatewayv2.HttpMethod.GET],
      integration: lambdaIntegration,
    });
    

    3 . S 3バケットとクラウドフロントディストリビューション


    あなたのCDKアプリのlibフォルダの中にfrontend で、index.html いくつかのHTMLコンテンツでファイルを
    <html>
      <body>
        Hello world
      </body>
    </html>
    
    以下のCDK依存関係をインポートしますlib/{your-stack-name}.ts ファイル
    import * as cloudfront from "@aws-cdk/aws-cloudfront";
    import * as s3 from "@aws-cdk/aws-s3";
    import * as iam from "@aws-cdk/aws-iam";
    import { Duration } from "@aws-cdk/core";
    import * as s3deploy from "@aws-cdk/aws-s3-deployment";
    
    今、プライベートのS 3のバケツを使用して標準的な静的なウェブサイトを作成することができますCloudfrontの配布とすべての間(iams、オアシスなど).
    const cloudfrontOAI = new cloudfront.OriginAccessIdentity(
      this,
      "cloudfrontOAI",
      {
        comment: `Allows CloudFront access to S3 bucket`,
      }
    );
    
    const websiteBucket = new s3.Bucket(this, "S3BucketForWebsiteContent", {
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      cors: [
        {
          allowedOrigins: ["*"],
          allowedMethods: [s3.HttpMethods.GET],
          maxAge: 3000,
        },
      ],
    });
    
    // uploads index.html to s3 bucket
    new s3deploy.BucketDeployment(this, "DeployWebsite", {
      sources: [s3deploy.Source.asset(`${__dirname}/frontend`)], // folder containing your html files
      destinationBucket: websiteBucket,
    });
    
    websiteBucket.addToResourcePolicy(
      new iam.PolicyStatement({
        sid: "Grant Cloudfront Origin Access Identity access to S3 bucket",
        actions: ["s3:GetObject"],
        resources: [websiteBucket.bucketArn + "/*"],
        principals: [cloudfrontOAI.grantPrincipal],
      })
    );
    
    const cloudfrontDistribution = new cloudfront.CloudFrontWebDistribution(
      this,
      "MyDistribution",
      {
        comment: "CDN for Web App",
        defaultRootObject: "index.html",
        viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
        originConfigs: [
          {
            s3OriginSource: {
              s3BucketSource: websiteBucket,
              originAccessIdentity: cloudfrontOAI,
            },
            behaviors: [
              {
                compress: true,
                isDefaultBehavior: true,
                defaultTtl: Duration.seconds(0),
                allowedMethods:
                  cloudfront.CloudFrontAllowedMethods.GET_HEAD_OPTIONS,
              },
            ],
          },
        ],
      }
    );
    

    別のCloudfront起源としてAPIゲートウェイを加えてください


    APIゲートウェイを別の起源として追加する/api/* CloudFrontディストリビューション(APIゲートウェイの原点がS 3の原点より上にあることを確認してください)/api/* 先読みをする)
    originConfigs: [
      {
        // make sure your backend origin is first in the originConfigs list so it takes precedence over the S3 origin
        customOriginSource: {
          domainName: `${httpApi.httpApiId}.execute-api.${this.region}.amazonaws.com`,
        },
        behaviors: [
          {
            pathPattern: "/api/*", // CloudFront will forward `/api/*` to the backend so make sure all your routes are prepended with `/api/`
            allowedMethods: cloudfront.CloudFrontAllowedMethods.ALL,
            defaultTtl: Duration.seconds(0),
            forwardedValues: {
              queryString: true,
              headers: ["Authorization"], // By default CloudFront will not forward any headers through so if your API needs authentication make sure you forward auth headers across
            },
          },
        ],
      },
      {
        s3OriginSource: {
          s3BucketSource: websiteBucket,
          originAccessIdentity: cloudfrontOAI,
        },
        behaviors: [
          {
            compress: true,
            isDefaultBehavior: true,
            defaultTtl: Duration.seconds(0),
            allowedMethods: cloudfront.CloudFrontAllowedMethods.GET_HEAD_OPTIONS,
          },
        ],
      },
    ];
    

    ハンドリングリダイレクトのためのラムダエッジ


    注意:一旦作成したらLambda@Edge削除するには時間がかかる.
    あなたのCDKアプリのlibフォルダの中にredirect で、index.ts 以下の内容でファイルをインクルードしますLambda@Edge):
    "use strict";
    
    exports.handler = (event: any, context: any, callback: any) => {
      const response = event.Records[0].cf.response;
      const request = event.Records[0].cf.request;
    
      /**
       * This function updates the HTTP status code in the response to 302, to redirect to another
       * path (cache behavior) that has a different origin configured. Note the following:
       * 1. The function is triggered in an origin response
       * 2. The response status from the origin server is an error status code (4xx or 5xx)
       */
    
      if (response.status == 404) {
        const redirect_path = `/`; //redirects back to root so to index.html
    
        response.status = 302;
        response.statusDescription = "Found";
    
        /* Drop the body, as it is not required for redirects */
        response.body = "";
        response.headers["location"] = [{ key: "Location", value: redirect_path }];
      }
    
      callback(null, response);
    };
    
    今、あなたのCDKにラムダリソースを追加します(後でこの分布の中でラムダを参照するときに、これがクラウドフロントディストリビューションリソースの前にあることを確認してください):
    const redirectLambda = new NodejsFunction(this, "redirectLambda", {
      entry: `${__dirname}/redirect/index.ts`,
      handler: "handler",
      runtime: Runtime.NODEJS_12_X,
    });
    
    最後にこれを関連付けるLambda@EdgeS 3の起源で
    {
        s3OriginSource: {
            s3BucketSource: websiteBucket,
            originAccessIdentity: cloudfrontOAI,
        },
        behaviors: [
            {
                compress: true,
                isDefaultBehavior: true,
                defaultTtl: Duration.seconds(0),
                allowedMethods:
                cloudfront.CloudFrontAllowedMethods.GET_HEAD_OPTIONS,
                lambdaFunctionAssociations: [
                    {
                        lambdaFunction: redirectLambda.currentVersion,
                        eventType: LambdaEdgeEventType.ORIGIN_RESPONSE,
                    },
                ],
            },
        ],
    },
    

    テストする


    次のエンドポイントを訪問してください/ こんにちは世界インデックスを返します.HTMLページ
    /api/helloworld マシンフレンドリーメッセージを返す必要があります
    /api/non-existent-endpoint バックエンドAPI

    そしてついに/non-existent-page 我々のインデックスに我々をリダイレクトしなければなりません.我々はWebアプリの中でそれを処理することができますので、HTML

    キーテイク

  • 使用してCloudFrontは、パスパターンに基づいて複数の起源から提供する
  • CloudFrontは、あなたのAPIパスがクラウドフロントで設定されたものと同じであることを確認します
  • デフォルトではCloudfrontはあなたのAPIにどんなヘッダも転送しないので、どのヘッダが転送されるのかを明示的にします
  • APIエラー応答を上書きするので、CloudFrontカスタムエラー構成を使用しないでください
  • 用途Lambda@EdgeS 3から404 NosuchKeyエラーをリダイレクトする
  • 代替案


    If Lambda@Edgeお好みに合わない場合は、代わりに以下の選択肢を試してみてください.
  • リダイレクトのための第3の起源として公開S 3バケット静的ウェブサイトを使用してください
  • あなたのAPIゲートウェイまたはALB
  • これが役に立つという望み!
    以下のコメント欄でのフィードバックと質問歓迎