🔨
[CDK] Lambdaを介さずAPIGateway+SNS連携する構成
APIを作るとき、まずAPIGW→Lambdaを検討してしまいがちですが、下記の場合などはAPI処理の非同期化を検討することになります。
- 数十秒以上要する処理
- 単純にユーザを待たせてしまう問題
- APIGW+Lambdaでは29秒でタイムアウト
- チャットボットAPI
- 数秒要するとタイムアウト
- LINEボットでは1秒
- Slackボットでは3秒
最も、チャットボットの方は下記の方法で対処できる可能性があるようです(今調べていてこのようなテクニックがあることを知りました)。
Slack Bot をサーバーレスで運用する時の、タイムアウト対策【小技】 - Qiita
今回はAPIGatewayにAmazon SNSを組み合わせて非同期化する構成を試してみたので紹介します。構築にCDKを使います。
構成図
APIGatewayがリクエストを受けるとSNSにPublishを行いレスポンスします。これによりLambdaの処理を待たないようになります。
環境
- typescript 4.7.4
- esbuild 0.14.48
- aws-cdk 2.30.0
- aws-cdk-lib 2.30.0
- constructs 10.1.43
セットアップ
CDKコード
下記のCDKコードを用意し、デプロイします。
lib/apigateway-stack.ts
import { Construct } from "constructs"
import * as cdk from "aws-cdk-lib"
export class ApigatewayStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props)
// Lambda関数
const lambdaFn = new cdk.aws_lambda_nodejs.NodejsFunction(this, "fn", {
runtime: cdk.aws_lambda.Runtime.NODEJS_16_X,
entry: "lambda-handler/from-sns-handler.ts",
})
// SNSトピック
const topic = new cdk.aws_sns.Topic(this, "topic", {})
topic.addSubscription(
new cdk.aws_sns_subscriptions.LambdaSubscription(lambdaFn)
)
// APIGW用IAMロール
const apiRole = new cdk.aws_iam.Role(this, "apiRole", {
assumedBy: new cdk.aws_iam.ServicePrincipal("apigateway.amazonaws.com"),
})
topic.grantPublish(apiRole)
// APIGW
const api = new cdk.aws_apigateway.RestApi(this, "api", {
cloudWatchRole: false,
})
api.root.addMethod(
"POST",
new cdk.aws_apigateway.AwsIntegration({
service: "sns",
region: "ap-northeast-1",
integrationHttpMethod: "POST",
action: "Publish",
options: {
credentialsRole: apiRole,
requestParameters: {
"integration.request.querystring.Message": "method.request.body",
"integration.request.querystring.TopicArn": `'${topic.topicArn}'`,
},
integrationResponses: [
{
statusCode: "200",
// SNSのレスポンスをそのまま返したい場合(パススルー)は記述しなくて良い
responseTemplates: {
"application/json": '{ message: "Success." }',
},
},
{
statusCode: "400",
selectionPattern: "4\\d{2}",
responseTemplates: {
"application/json": '{ message: "Invalid request." }',
},
},
{
statusCode: "500",
selectionPattern: "5\\d{2}",
responseTemplates: {
"application/json": '{ message: "Internal server error." }',
},
},
],
},
}),
{
methodResponses: [
{
statusCode: "200",
},
{
statusCode: "400",
},
{
statusCode: "500",
},
],
}
)
}
}
bin/aws-playground.ts
#!/usr/bin/env node
import "source-map-support/register"
import * as cdk from "aws-cdk-lib"
import { ApigatewayStack } from "../lib/apigateway-stack"
const app = new cdk.App()
new ApigatewayStack(app, "ApigatewayStack", {
env: { region: "ap-northeast-1" },
})
Lambda関数コード
イベントを出力するだけのシンプルなLambda関数です。
lambda-handler/from-sns-handler.ts
import { SNSEvent } from "aws-lambda"
export const handler = async (event: SNSEvent): Promise<void> => {
console.log(JSON.stringify({ event }))
}
動作確認する
テストリクエストを送信
curl -X POST \
-H "Content-Type: application/json" \
-d '{"message": "Hello apigw+sns!"}' https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/
# => { message: "Success." }
CloudWatch Logsを確認
2022-07-02T11:29:35.076Z xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx INFO
{
"event": {
"Records": [
{
"EventSource": "aws:sns",
"EventVersion": "1.0",
"EventSubscriptionArn": "arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:xxxxxxxxxxxxxxxxxxx:xxxxxxxxxxxxxxxxxx",
"Sns": {
"Type": "Notification",
"MessageId": "xxxxxxxxxxxxxxxxxx",
"TopicArn": "arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxx",
"Subject": null,
"Message": "{\"message\":\"Hello apigw+sns!\"}",
"Timestamp": "2022-07-02T11:29:34.694Z",
"SignatureVersion": "1",
"Signature": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"SigningCertUrl": "https://sns.ap-northeast-1.amazonaws.com/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"UnsubscribeUrl": "https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"MessageAttributes": {}
}
}
]
}
}
"Message": "{\"message\":\"Hello apigw+sns!\"}"
が来ていることが確認できました。
参考
- 【AWS CDK】API Gateway で S3 をプロキシしてオブジェクトをダウンロードしてみた | DevelopersIO
- [AWS]API Gatewayの本文マッピングテンプレートを理解する | DevelopersIO
- API GatewayからSNSトピックにメッセージ発行してメール通知 - Qiita
- Amazon API Gateway - Qiita
- [小ネタ]Lambda統合を使用したAPI Gatewayのエクスポート→インポートの注意点 | DevelopersIO
- API Gateway mapping template and access logging variable reference - Amazon API Gateway
- Amazon API Gateway API request and response data mapping reference - Amazon API Gateway
- Slack Bot をサーバーレスで運用する時の、タイムアウト対策【小技】 - Qiita
Discussion
こんなやり方があるのですね!参考になりました。ちなみに、API Gateway から Lambda を呼ぶときに非同期起動モードもあるのですが、こちらでは要件を満たせない事情がありましたか?
コメントありがとうございます。気づくのが遅れました。
なるほど、この機能を知らなかったです。
確実に選択肢に入ってくる機能に思われるので、機会があれば検証したいと思います。情報ありがとうございます。