LoginSignup
1
2

More than 1 year has passed since last update.

API Gatewayにカスタムドメインを割り当てる。

Last updated at Posted at 2021-11-24

API GWにカスタムドメインを設定する。

前提条件

  • ドメインを取得済み(今回はFreenom利用)。
  • Route53にホストゾーン作成済み。
  • FreenomへNSレコードの値を登録済み。
  • 東京リージョンでAWS ACMで証明書を発行済み。(Route53へCNAMEも登録済み)
  • CDKのversion 1.134.0 (build dd5e12d)

必要なリソースの準備と設定(CDK)

import * as cdk from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda';
import * as apigw from '@aws-cdk/aws-apigateway';
import * as acm from '@aws-cdk/aws-certificatemanager';
import * as route53 from '@aws-cdk/aws-route53';
import * as route53Tragts from '@aws-cdk/aws-route53-targets';

export class CustomdomainapiStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // const
    const ApigwCustomDomainName = "my-custom-domain.tk";
    const ApigwCertificateArn = "arn:aws:acm:ap-northeast-1:************:certificate/*********";
    const hostzoneId = "*******************";
    const apiName = "test211124";
    const apiStageName = "v1";
    const apiUsagePlanName = "testusage211124"
    const apiKeyName = "testusage211124"
    const lambdaName = "test211124"

    // == Lambda ==
    const testLambda = new lambda.Function(this, `Lambda`, {
      code: lambda.Code.fromAsset(`src/${lambdaName}`),
      functionName: lambdaName,
      handler: "index.handler",
      runtime: lambda.Runtime.NODEJS_14_X,
    })

    // == API Gateway ==
    const testApi = new apigw.RestApi(this, "APIGW", {
      restApiName: apiName,
      deployOptions: {
        stageName: apiStageName,
      },
      endpointTypes: [
        apigw.EndpointType.REGIONAL
      ],
      apiKeySourceType: apigw.ApiKeySourceType.HEADER,
      domainName: {
        domainName: ApigwCustomDomainName,
        certificate: acm.Certificate.fromCertificateArn(this, "Certificate", ApigwCertificateArn),
        securityPolicy: apigw.SecurityPolicy.TLS_1_2,
        endpointType: apigw.EndpointType.REGIONAL,
      }
    });

    // apiUsagePlan
    const apiUsagePlan = testApi.addUsagePlan("UsagePlan", {
      name: apiUsagePlanName,
      throttle: {
        rateLimit: 10,
        burstLimit: 2
      }
    });
    apiUsagePlan.addApiStage({
      stage: testApi.deploymentStage
    })

    // apiKey
    const key = new apigw.ApiKey(this, "Key", {
      apiKeyName: apiKeyName
    });
    apiUsagePlan.addApiKey(key);

    // Path
    const testPath = testApi.root.addResource("test");
    const deeptestPath = testPath.addResource("test");

    // Method
    // root path
    testApi.root.addMethod("GET", new apigw.LambdaIntegration(testLambda), {
      methodResponses: [
        {
          statusCode: "200",
        }
      ],
      apiKeyRequired: true
    });
    // /test path
    testPath.addMethod("GET", new apigw.LambdaIntegration(testLambda), {
      methodResponses: [
        {
          statusCode: "200",
        }
      ],
      apiKeyRequired: true
    });
    // /test/test path
    deeptestPath.addMethod("GET", new apigw.LambdaIntegration(testLambda), {
      methodResponses: [
        {
          statusCode: "200",
        }
      ],
      apiKeyRequired: true
    });

    const hostZone = route53.PublicHostedZone.fromHostedZoneAttributes(this, "HostZone",{
        hostedZoneId: hostzoneId,
        zoneName: ApigwCustomDomainName,
      }
    );
    new route53.ARecord(this, "ARecord", {
        zone: hostZone,
        recordName: ApigwCustomDomainName,
        target: route53.RecordTarget.fromAlias(
            new route53Tragts.ApiGateway(testApi),
        ),
      }
    );
  }
}

200返せば何でもいい(Mockでも十分)だが、一応Lambdaのコードも。

src/test211124/index.js
"use strict";

exports.handler = main;
async function main(event) {
  try {
    console.log("start function");
    console.log(`event : ${JSON.stringify(event)}`);
    return {
        statusCode: 200,
        body: "hello"
    };
  } catch (err) {
    console.error(`[Error] ${JSON.stringify(err)}`);
    return {
        statusCode: 500,
        body: JSON.stringify(err.message)
    };
  }
}

検証

postmanで実行。

  • https://my-custom-domain.tk/
  • https://my-custom-domain.tk/test
  • https://my-custom-domain.tk/test/test

上記にアクセスできれば成功。

手動でRoute53のホストゾーンにAレコードを作成する場合

レコード作成時にエイリアスを指定しAPI GWの選択一覧に出てくるAPIエンドポイントは、カスタムエンドポイントのコンソールの「設定」タブに表示されているもので、ステージ作成時に発行されているものとは異なる。

パス(Optional)

カスタムドメインのAPIマッピングの際に指定可能でOptionalとなっているパス。
ルートの位置を指定するものと思っていたが、イメージと違ったのでメモ。
(今考えてみると複数指定できる時点で元の考え方はおかしいのだが、、)

image.png

例えば、以下のようなAPIを作成し、2つのリソースを持たせたとする。

  • http://abcdef012.execute-api.ap-northeast-1.amazonaws.com/v1/
  • http://abcdef012.execute-api.ap-northeast-1.amazonaws.com/v1/test

このAPIにカスタムドメインmy-custom-domain.comを設定し、上記のようにマッピング(パス指定なし)すると、それぞれ以下のようにしてアクセスする必要がある。

  • https://my-custom-domain.tk/
  • https://my-custom-domain.tk/test

つまりデフォルトでv1にマッピングされるのでカスタムドメインでのアクセス時に/v1/を入れる必要がなくなる。
ここで、v1を明示して指定させたい時、このパス(Optional)にてv1と書く。

image.png

すると、上記のURLではアクセスできなくなり、以下のように指定するようになる。

  • https://my-custom-domain.tk/v1
  • https://my-custom-domain.tk/v1/test

もちろんパス部には何を書いてもいい(/とかは入れられない)のでパスにv1ではなくhogeと入力すると

  • https://my-custom-domain.tk/hoge
  • https://my-custom-domain.tk/hoge/test

となる。

それいつ使うの?

例えば、リバプロが前段に置いてあり、特定文字列(例えばapi)がパスの先頭に入っていたらこちらに振り分けるみたいな構成がとられていたりすると、必ずhttps://my-custom-domain.tk/api/という始まり方でアクセスされることになる。このapiをステージ名に設定してあげればいいのは確かにそうだが、ステージの使い方的にイケてない。(APIのversionをパスに設定することになる。)
上記の例でhogeをパスに指定すると、APIのパス構成の中には影響せずにパスを指定することができることからここに apiをいれてあげれば、その影響を無視することができそう。

上ではリバプロとしたが、CloudFrontを置いて、S3をオリジンとする静的Webページのコンテンツとそこで利用されるAPIのドメインを揃える場合にも同一の現象が起こる。CloudFront Distributionにおいて、コンテンツのパスをルートとし、apiコールのパスを/apiのように切り分ける場合である。
(lambda@edgeとかでパスを削り落としてもいいがコストになりそう。)

複数のマッピングができるということ

一つのカスタムドメインに対して、複数のAPIのstageを紐づけてパスで切り分けることができる。

参考

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2