🎃

APIGatewayからLambdaを非同期起動する

2023/04/06に公開

APIGateway+LambdaでLambdaを非同期コールする構成をCDKで構築する。

どういう時に使うのか

APIGateway+Lambdaで同期レスポンスする場合、タイムアウト設定は最大29秒なので、それより時間のかかる処理であれば非同期化を検討する必要がある。

また、Slackボットでは3秒LINEボットでは1秒以内でレスポンスしなければタイムアウトエラー扱いになるため、非同期化を考えることが多い。

環境

  • node 18.14.2
  • aws-cdk-lib 2.70.0
  • constructs 10.1.289
  • typescript 5.0.2

コード

CDKコード:

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);

    // APIGW Lambda関数
    const apiFn = new cdk.aws_lambda_nodejs.NodejsFunction(this, "apiFn", {
      runtime: cdk.aws_lambda.Runtime.NODEJS_18_X,
      entry: "src/lambda/async-api-handler.ts",
      bundling: {
        sourceMap: true,
      },
      timeout: cdk.Duration.seconds(29),
    });

    // APIGW
    const api = new cdk.aws_apigateway.RestApi(this, "api", {
      deployOptions: {
        stageName: "api",
        loggingLevel: cdk.aws_apigateway.MethodLoggingLevel.INFO,
      },
    });
    api.root.addMethod(
      "POST",
      new cdk.aws_apigateway.LambdaIntegration(apiFn, {
        proxy: false,
        requestParameters: {
          "integration.request.header.X-Amz-Invocation-Type": "'Event'",
        },
        passthroughBehavior:
          cdk.aws_apigateway.PassthroughBehavior.WHEN_NO_TEMPLATES,
        requestTemplates: {
          // AWSマネコンのAPIGW>統合リクエスト>マッピングテンプレート>application/json>テンプレートの生成>メソッドリクエストのパススルーをベースに記述
          "application/json": `
          ##  See http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
          ##  This template will pass through all parameters including path, querystring, header, stage variables, and context through to the integration endpoint via the body/payload
          #set($allParams = $input.params())
          {
          "bodyJson" : $input.json('$'),
          "params" : {
          #foreach($type in $allParams.keySet())
              #set($params = $allParams.get($type))
          "$type" : {
              #foreach($paramName in $params.keySet())
              "$paramName" : "$util.escapeJavaScript($params.get($paramName))"
                  #if($foreach.hasNext),#end
              #end
          }
              #if($foreach.hasNext),#end
          #end
          },
          "stage-variables" : {
          #foreach($key in $stageVariables.keySet())
          "$key" : "$util.escapeJavaScript($stageVariables.get($key))"
              #if($foreach.hasNext),#end
          #end
          },
          "context" : {
              "account-id" : "$context.identity.accountId",
              "api-id" : "$context.apiId",
              "api-key" : "$context.identity.apiKey",
              "authorizer-principal-id" : "$context.authorizer.principalId",
              "caller" : "$context.identity.caller",
              "cognito-authentication-provider" : "$context.identity.cognitoAuthenticationProvider",
              "cognito-authentication-type" : "$context.identity.cognitoAuthenticationType",
              "cognito-identity-id" : "$context.identity.cognitoIdentityId",
              "cognito-identity-pool-id" : "$context.identity.cognitoIdentityPoolId",
              "http-method" : "$context.httpMethod",
              "stage" : "$context.stage",
              "source-ip" : "$context.identity.sourceIp",
              "user" : "$context.identity.user",
              "user-agent" : "$context.identity.userAgent",
              "user-arn" : "$context.identity.userArn",
              "request-id" : "$context.requestId",
              "resource-id" : "$context.resourceId",
              "resource-path" : "$context.resourcePath"
              }
          }
          
`.trim(),
        },
        integrationResponses: [
          {
            statusCode: "202",
          },
        ],
      }),
      {
        methodResponses: [
          {
            statusCode: "202",
          },
        ],
      }
    );
  }
}

Lambdaコード:

import { APIGatewayEvent, Callback, Context } from "aws-lambda";

export const handler = async (
  event: APIGatewayEvent,
  _context: Context,
  _callback: Callback
): Promise<void> => {
  console.log(event);
};

実行する

デプロイ:

npx cdk deploy

リクエストコマンドを実行:

curl -X POST -H "Content-Type: application/json" https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/ -d '{"key":"value"}'

CloudWatch Logsへの出力結果:

INFO	{
  bodyJson: { key: 'value' },
  params: {
    path: {},
    querystring: {},
    header: {
      Accept: '*/*',
      'CloudFront-Forwarded-Proto': 'https',
      'CloudFront-Is-Desktop-Viewer': 'true',
      'CloudFront-Is-Mobile-Viewer': 'false',
      'CloudFront-Is-SmartTV-Viewer': 'false',
      'CloudFront-Is-Tablet-Viewer': 'false',
      'CloudFront-Viewer-ASN': 'xxxxx',
      'CloudFront-Viewer-Country': 'JP',
      'content-type': 'application/json',
      Host: 'xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com',
      'User-Agent': 'curl/x.xx.x',
      Via: '2.0 xxxxxxxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)',
      'X-Amz-Cf-Id': 'xxxxxxxxxxxxxxxxxxxxxxx',
      'X-Amzn-Trace-Id': 'Root=x-xxxxxxxxxx-xxxxxxxxxxx',
      'X-Forwarded-For': '***.***.***.***, ***.***.***.***',
      'X-Forwarded-Port': '443',
      'X-Forwarded-Proto': 'https'
    }
  },
  'stage-variables': {},
  context: {
    'account-id': '',
    'api-id': 'xxxxxxxxxx',
    'api-key': '',
    'authorizer-principal-id': '',
    caller: '',
    'cognito-authentication-provider': '',
    'cognito-authentication-type': '',
    'cognito-identity-id': '',
    'cognito-identity-pool-id': '',
    'http-method': 'POST',
    stage: 'api',
    'source-ip': '***.***.***.***',
    user: '',
    'user-agent': 'curl/x.xx.x',
    'user-arn': '',
    'request-id': 'xxxxxxx-xxxxxx-xxxxx-xxxxxx',
    'resource-id': 'xxxxxxxxxx',
    'resource-path': '/'
  }
}

参考

Discussion