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

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

### 前提条件

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

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

```ts
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のコードも。

```js: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/3ddd0e30-f2ce-6600-cc75-32342cf8c612.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/825b779e-4d63-7a40-f5bb-db1df0bd96f7.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とかでパスを削り落としてもいいがコストになりそう。)

https://dev.classmethod.jp/articles/cloudfront-in-front-on-websocket-api-gateway/

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

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

## 参考

*  https://wp-kyoto.net/set-api-key-to-api-gateway-by-aws-cdk/
*  https://dev.classmethod.jp/articles/aws-cdk-all-resources-for-api-gateway/