0
0

API GatewayからLambdaを通さずにS3へデータを入れる処理をCDKで書いたメモ

Posted at

概要

API GatewayにPutしたデータをS3に格納してみたメモ。

API GatewayからLambdaを通さずに直接DynamoDBにデータを入れる処理をCDKで書いたメモと同様、インテグレーションでマッピングする。
ソースコード

CDK

import { RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib';
import { AwsIntegration, Cors, MethodLoggingLevel, PassthroughBehavior, RestApi } from 'aws-cdk-lib/aws-apigateway';
import { Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
import { Bucket, EventType } from 'aws-cdk-lib/aws-s3';
import { Construct } from 'constructs';
import { Code, Function, Runtime } from 'aws-cdk-lib/aws-lambda';
import { LambdaDestination } from 'aws-cdk-lib/aws-s3-notifications';


export class ApiGatewayProxyToS3ByCdkStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps & { projectId: string }) {
    super(scope, id, props);

    const projectName: string = props.projectId;
    const bucket = this.createBucket(projectName);
    const restApiRole = this.createRole(bucket);
    const restApi = this.createRestApi(projectName);

    // リソースを作成する `/test`
    const test = restApi.root.addResource('test');
    test.addMethod('PUT', this.createIntegration(restApiRole, bucket), {
      requestParameters: {},
      methodResponses: [this.createOkResponse(), this.create400Response(), this.createErrorResponse()],
    });
    const lambdaParamsDefault = {
      runtime: Runtime.NODEJS_20_X,
      handler: 'index.handler',
    };

    const lambdaFunction = new Function(this, 'lambdaFunction', {
      ...lambdaParamsDefault,
      code: Code.fromInline(`exports.handler = async (event) => { console.log(JSON.stringify(event)); };`),
    });
    bucket.addEventNotification(EventType.OBJECT_CREATED_PUT, new LambdaDestination(lambdaFunction));
  }
  private createBucket(projectName: string) {
    const bucket = new Bucket(this, 'Bucket', { bucketName: `${projectName}-proxy-to-bucket`, removalPolicy: RemovalPolicy.DESTROY });
    return bucket;
  }
  private createRole(bucket: Bucket) {
    const restApiRole = new Role(this, 'Role', { assumedBy: new ServicePrincipal('apigateway.amazonaws.com'), path: '/' });
    bucket.grantReadWrite(restApiRole);
    return restApiRole;
  }
  private createRestApi(projectName: string) {
    const restApi = new RestApi(this, 'RestApi', {
      restApiName: `${projectName}-api`,
      deployOptions: {
        stageName: 'v1',
        loggingLevel: MethodLoggingLevel.INFO,
        dataTraceEnabled: true,
      },
    });
    return restApi;
  }
  private createIntegration(restApiRole: Role, bucket: Bucket) {
    const responseParameters = {
      'method.response.header.Access-Control-Allow-Headers': "'Content-Type,Authorization'",
      'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,POST,PUT,GET,DELETE'",
      'method.response.header.Access-Control-Allow-Origin': "'*'",
    };
    return new AwsIntegration({
      service: 's3',
      integrationHttpMethod: 'PUT',
      // アップロード先を指定する
      path: `${bucket.bucketName}/{folder}/{object}.json`,
      options: {
        credentialsRole: restApiRole,
        passthroughBehavior: PassthroughBehavior.WHEN_NO_MATCH,
        // https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/http-api-logging-variables.html
        requestParameters: {
          // リクエストのEpocTime を 統合リクエストのパスパラメータ folder にマッピングする
          'integration.request.path.folder': 'context.requestTimeEpoch',
          // API Gateway が API リクエストに割り当てる IDを 統合リクエストの object にマッピングする。
          'integration.request.path.object': 'context.requestId',
        },
        integrationResponses: [
          {
            statusCode: '202',
            responseParameters: {
              ...responseParameters,
              'method.response.header.Timestamp': 'integration.response.header.Date',
              'method.response.header.Content-Length': 'integration.response.header.Content-Length',
              'method.response.header.Content-Type': 'integration.response.header.Content-Type',
            },
          },
          {
            statusCode: '400',
            selectionPattern: '4\\d{2}',
            responseParameters,
          },
          {
            statusCode: '500',
            selectionPattern: '5\\d{2}',
            responseParameters,
          },
        ],
      },
    });
  }
  private createOkResponse() {
    return {
      statusCode: '202',
      responseParameters: {
        'method.response.header.Timestamp': true,
        'method.response.header.Content-Length': true,
        'method.response.header.Content-Type': true,
        'method.response.header.Access-Control-Allow-Headers': true,
        'method.response.header.Access-Control-Allow-Methods': true,
        'method.response.header.Access-Control-Allow-Origin': true,
      },
    };
  }
  private create400Response() {
    return {
      statusCode: '400',
      responseParameters: {
        'method.response.header.Access-Control-Allow-Headers': true,
        'method.response.header.Access-Control-Allow-Methods': true,
        'method.response.header.Access-Control-Allow-Origin': true,
      },
    };
  }
  private createErrorResponse() {
    return {
      statusCode: '500',
      responseParameters: {
        'method.response.header.Access-Control-Allow-Headers': true,
        'method.response.header.Access-Control-Allow-Methods': true,
        'method.response.header.Access-Control-Allow-Origin': true,
      },
    };
  }
}

実行

PUT https://hoge.execute-api.ap-northeast-1.amazonaws.com/v1/test/

{
  "name": "test",
  "age": 20
}

実行結果

戻り値として202がかえることを確認。

HTTP/1.1 202 Accepted
Content-Type: application/json
Content-Length: 0
Connection: close
Date: Tue, 13 Feb 2024 16:36:34 GMT
x-amzn-RequestId: 02c8b819-390e-46b6-9b73-baf9d7c32815
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type,Authorization
x-amzn-Remapped-Content-Length: 0
x-amz-apigw-id: TFUW8GiNNjMEDpw=
Access-Control-Allow-Methods: OPTIONS,POST,PUT,GET,DELETE
Timestamp: Tue, 13 Feb 2024 16:36:35 GMT
X-Amzn-Trace-Id: Root=1-65cb9a92-6000c0252286a9f4603bc198
X-Cache: Miss from cloudfront
Via: 1.1 xxxx.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: NRT51-C1
X-Amz-Cf-Id: K2eEwSteJfEjgoqryMCDHdl7qceAs4sKiM4P1K6-pmPkTSdMhkmAbA==

S3に格納されることを確認。

image.png

S3トリガーで取得できるイベント

イベントログが出力されていることを確認。

{
    "Records": [
        {
            "eventVersion": "2.1",
            "eventSource": "aws:s3",
            "awsRegion": "ap-northeast-1",
            "eventTime": "2024-02-13T16:36:34.598Z",
            "eventName": "ObjectCreated:Put",
            "userIdentity": {
                "principalId": "AWS:xxx:BackplaneAssumeRoleSession"
            },
            "requestParameters": {
                "sourceIPAddress": "xx.xx.xxx.xxx"
            },
            "responseElements": {
                "x-amz-request-id": "7DBKNF5FG9H7CXXA",
                "x-amz-id-2": "ehR9bsjJN9pjFSt/aoOpH646Hz++loxJYzyLzaULAYOPFji10+TYcNAPQGvrFbrEArLT1yZfWSoZn/CL08WFryJoqg58Z9Vb"
            },
            "s3": {
                "s3SchemaVersion": "1.0",
                "configurationId": "MTZjYmU1MDUtOThiZS00OGMzLWFhOTEtYmNhNzVlMmI2OTVj",
                "bucket": {
                    "name": "hoge-bucket",
                    "ownerIdentity": {
                        "principalId": "xxxxxx"
                    },
                    "arn": "arn:aws:s3:::hoge-bucket"
                },
                "object": {
                    "key": "1707842194527/02c8b819-390e-46b6-9b73-baf9d7c32815.json",
                    "size": 36,
                    "eTag": "37f2d6e6f0d7af5ca3967198d35bbde5",
                    "sequencer": "0065CB9A928F560569"
                }
            }
        }
    ]
}

S3から取得する。

ソースコード

参考

x-amazon-apigateway-integration.requestParameters オブジェクト
【AWS CDK】API Gateway で S3 をプロキシしてオブジェクトをアップロードしてみた
CDKを使って既存S3バケットのPUTイベントをトリガーにLambda関数を実行しようとしたらハマった話
Lambda(node.js)からS3のオブジェクトをGetObjectCommandで取得する – AWS SDK for JavaScript v3

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0