概要
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に格納されることを確認。
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