🐈

API Gateway + Lambdaを非同期で呼び出す by CDK

2021/03/27に公開

先日API Gateway & Lambdaの構成でちょっと重めの処理を実装してたところ思った以上に時間がかかってタイムアウトになってしまうというミスをやらかしてしまった(API Gateway側のタイムアウト値29秒に引っかかった)

こういう場合は素直にSQSのFIFOキュー使う形にすればいいんだけども、とりあえず雑に動いてくれればいいやっていう実験的な処理だったのでLambdaを非同期に呼ぶ形に変更。その際何回か試行錯誤してちょっと困ったので忘れないようメモがてらサンプル用のコードを残しておく。

サンプルで作るもの

POSTで投げたリクエストボディの中身をログに出力した上でそのまま返すだけのシンプルなAPI。呼び出す際のエンドポイントによって同期/非同期を選択できるようにしておく。

/sync → 同期呼び出し
/async → 非同期呼び出し

picture 2

環境

node: v16.10.0
aws-cli: 2.0.10
aws-cdk: 1.124.0

初期設定

mkdir cdk-lambda-sample && cd cdk-lambda-sample 
cdk init app --language typescript

mkdir lambda
touch lib/index.ts

npm install @aws-cdk/aws-apigateway @aws-cdk/aws-lambda @aws-cdk/aws-lambda-nodejs
npm install --save-dev @types/aws-lambda

Lambda

lib/lambda/index.ts
import { APIGatewayProxyHandlerV2, APIGatewayProxyResultV2 } from "aws-lambda";

export const handler: APIGatewayProxyHandlerV2 = async (event): Promise<APIGatewayProxyResultV2> => {
  console.log("request body", event.body)

  return {
    "isBase64Encoded": false,
    "statusCode": 200,
    "body": event.body,
    "headers": {
      "content-type": "application/json"
    }
  }
}

Stack

lib/cdk-lambda-sample-stack.ts
import * as cdk from "@aws-cdk/core"
import { Runtime } from "@aws-cdk/aws-lambda"
import { NodejsFunction } from "@aws-cdk/aws-lambda-nodejs"
import * as apigateway from '@aws-cdk/aws-apigateway';
import { Duration } from "@aws-cdk/core";

export class CdkLambdaSampleStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props)
    
    const lambda = new NodejsFunction(this, "CdkLambdaSampleStack", {
      entry: "lib/lambda/index.ts",
      handler: "handler",
      runtime: Runtime.NODEJS_14_X,
      timeout: Duration.seconds(180)
    })

    const api = new apigateway.RestApi(this, 'CdkLambdaSampleApi', { cloudWatchRole: false })

    // 同期呼び出しの設定
    api.root.addResource('sync').addMethod('POST', new apigateway.LambdaIntegration(lambda))

    // 非同期呼び出しの設定
    api.root.addResource('async').addMethod(
      'POST',
      new apigateway.LambdaIntegration(lambda, {
        // Webコンソール上では「Lambda プロキシ統合の使用」にあたる項目
        // デフォルトのtrueの場合、非同期で呼び出したレスポンスがLambdaの形式ではないと怒られてエラーになってしまう
        proxy: false,
        requestParameters: {
          // Lambdaを非同期で呼び出すためにヘッダーに追記
          'integration.request.header.X-Amz-Invocation-Type': "'Event'"
        },
        integrationResponses: [
          {
            // Lambdaを非同期で呼び出した場合のステータスコードは202になるので合わせる
            statusCode: '202',
          }
        ]
      }),
      {
        methodResponses: [
          {
          // Lambdaを非同期で呼び出した場合のステータスコードは202になるので合わせる
            statusCode: '202',
          }
        ],
    })
  }
}

動かしてみる

cdk deploy

# 同期実行 : {"id": 123456789", "name":"Sushi"} がそのまま返される
curl -X POST {Outputsで出力されたエンドポイント}/sync -d '{"id": 123456789, "name":"Sushi"}'

# 非同期実行 : bodyの中身はないがステータスが202で帰ってくる。CloudWatchで見ると実行履歴が確認できるはず
curl -i -X POST {Outputsで出力されたエンドポイント}/async -d '{"id": 123456789, "name":"Sushi"}'

Webコンソール上の設定のやり方しらべてからCDKのコードに落とし込んだり、と微妙にハマったけどとりあえずこれで問題なく動いてる(あんまり需要はなさそうだけど)

Discussion