バージョン管理された人

subversionで管理されてます

CDK による API Gateway と SNS の連携

API Gateway から SNS への連携は Lambda + SQS を利用しなくても直接 API Gateway から SNS へリクエストを流せる。

まずは、 API Gateway と通知先 SNS topic を用意する:

import * as sns from "@aws-cdk/aws-sns";
import * as core from "@aws-cdk/core";
import * as iam from "@aws-cdk/aws-iam";
import * as apigateway from "@aws-cdk/aws-apigateway";

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

    const topic = new sns.Topic(this, "topic");
    const gateway = new apigateway.RestApi(this, "gateway");

    // ...

API Gateway から SNS topic へ publish するためには IAM ロールを用意し、 SNS topic にそのロールへ publish する許可を与え、 API Gateway が publish するときに、その権限を利用するようにする必要がある:

// ...

const role = new iam.Role(this, "role", {
  // API Gateway がこのロールに Assume するため、 apigateway.amazonaws.com
  // プリンシパルを指定する。
  assumedBy: new iam.ServicePrincipal("apigateway.amazonaws.com"),
});
// role に publish する権限を与える
topic.grantPublish(role);

// ...

さて、ここまでは簡単なのだが、問題は SNSAPI Gateway を連携する部分。 ここがドキュメントがなくて最も難解な箇所となる。

連携させるには apigateway.AwsIntegration を利用する。 これは API Gateway と任意の AWS サービスを連携させる際に利用するものなのだが、任意のサービスと連携させる分、設定項目が多く、抽象的で、また、 AWS 側の API の仕様を調べなければならないため、設定内容を調べる難易度が高くなる。

今回は SNS と連携して API Gateway から SNS へリクエストを流すため、 SNS の Publish を行うまでの設定について記載する:

// ...

// ステータスコードの詳細については次を参考に
// https://docs.aws.amazon.com/sns/latest/api/API_Publish.html#API_Publish_Errors
const statusCodes = ["200", "400", "403", "404", "500"];
const integration = new apigateway.AwsIntegration({
  // ここに連携するサービス名を記載する。
  // 今回は SNS なので、 `"sns"` を指定する。
  service: "sns",
  // API 発行先のルートを指定する。
  // 用は `https://example.com/${path}` の `${path}` の部分をここに指定する。
  // SNS に Publish する場合は `"/"` を 指定すればよい(なお、空文字列 `""` を指定したら `path` を指定しろとエラーになった。おそらく JavaScript の `falsy` な値に引っかかっているからだと思われる)。
  path: "/",
  // proxy は通してないので false に設定する
  proxy: false,
  // 今回は API Gateway に POST されたものを SNS topic に流すように設定する。
  // 他のメソッドを利用するなら、別途インテグレーションしてほしい
  integrationHttpMethod: "POST",
  options: {
    // ここに、先程用意したロールを指定する。
    // このロールに API Gateway が AssumeRole して Publish する
    credentialsRole: role
    // AWS API のリクエストパラメータを設定する
    requestParameters: {
      // API Gateway から SNS にリクエストを流すとき、リクエストをエンコードしているので、API Gateway から SNS へ出すリクエストの HTTP ヘッダに Content-Type を生やす
      "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'",
    },
    // API Gateway から連携先に出すリクエストのテンプレートを指定する。
    // API Gateway にくるリクエストの Content-Type 毎に設定できる。
    requestTemplates: {
      // 今回は最も一般的であろうと思われる "application/json" のみを設定する。
      // SNS のリクエストパラメータについての詳細は次のドキュメントを見て欲しい。
      // https://docs.aws.amazon.com/sns/latest/api/API_Publish.html
      "application/json": [
        "Action=Publish",
        `TopicArn=$util.urlEncode('${topic.topicArn}')`,
        "Message=$util.urlEncode($input.body)",
        // FIFO topic を利用するなら MessageGroupId を指定してやる必要がある。
        // "MessageGroupId=<グループID>",
      ].join("&")
    },
    // 上の requestTemplates で指定してない Content-Type なデータが送られてきたら 415 を返すようにする
    passthroughBehavior: apigateway.PassthroughBehavior.WHEN_NO_TEMPLATES,
    // 最終的にクライアントに返る統合レスポンスの設定を行う。
    // 設定項目については次を参照して欲しい
    // https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.IntegrationResponse.html
    integrationResponses: statusCodes.map(statusCode => ({ statusCode })),
  },
});

// ...

最後に、ルートを生やす:

// ...

// バックエンドから返ってくる中で取り扱いたいレスポンスを指定する:
const methodResponses: apigateway.MethodResponses[] = statusCodes.map(
  (statusCode) => ({ statusCode })
);
gateway.root.addMethod("POST", intergration, { methodResponses });

// ...