この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
テントの中から失礼します、CX事業本部のてんとタカハシです!
API Gateway では、S3 をプロキシして、オブジェクトのダウンロードやアップロード、削除など様々なことができます。単純な CRUD 操作であれば、Lambda を実装することなく実現可能なので結構便利です。
本記事では、API Gateway で S3 をプロキシして、Bucket に格納されているオブジェクトをダウンロードする REST API を CDK で作成してみます。
下記のチュートリアルをガッツリ参考していますので、細かい説明についてはこちらをご参照ください。
他の記事で、オブジェクトのアップロード、削除、一覧取得にもチャレンジしています。
本記事及び、上記の記事にて試した機能を全て搭載した REST API のソースコードを下記リポジトリに置いています。
GitHub - iam326/api-gateway-proxy-to-s3-by-cdk
環境
環境は下記の通りです。
$ cdk --version
1.102.0 (build a75d52f)
$ yarn --version
1.22.10
$ node --version
v14.7.0
実装
- オブジェクトを格納するための S3 Bucket を作成する
- API Gateway で REST API を作成する
- 上記 API にリソース
/users/{userId}/files/{fileName}
を作成する - 上記リソースに GET メソッドを作成して、S3 をプロキシする
lib/api-gateway-proxy-to-s3-by-cdk-stack.ts
import * as cdk from '@aws-cdk/core';
import * as apigateway from '@aws-cdk/aws-apigateway';
import * as iam from '@aws-cdk/aws-iam';
import * as s3 from '@aws-cdk/aws-s3';
export class ApiGatewayProxyToS3ByCdkStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const projectName: string = this.node.tryGetContext('projectName');
// ★ S3
const bucket = new s3.Bucket(this, 'Bucket', {
bucketName: `${projectName}-bucket`,
});
// ★ API Gateway
const restApiRole = new iam.Role(this, 'Role', {
assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'),
path: '/',
});
bucket.grantReadWrite(restApiRole);
const restApi = new apigateway.RestApi(this, 'RestApi', {
restApiName: `${projectName}-api`,
deployOptions: {
stageName: 'v1',
loggingLevel: apigateway.MethodLoggingLevel.INFO,
dataTraceEnabled: true,
},
defaultCorsPreflightOptions: {
allowOrigins: apigateway.Cors.ALL_ORIGINS,
allowMethods: ['POST', 'OPTIONS', 'PUT', 'DELETE'],
statusCode: 200,
},
});
// リソースを作成する `/users/{userId}/files/{fileName}`
const users = restApi.root.addResource('users');
const userId = users.addResource('{userId}');
const files = userId.addResource('files');
const fileName = files.addResource('{fileName}');
// オブジェクトをダウンロードするための GET メソッドを作成する
fileName.addMethod(
'GET',
new apigateway.AwsIntegration({
service: 's3',
integrationHttpMethod: 'GET',
// ダウンロード先を指定する
path: `${bucket.bucketName}/{folder}/{object}`,
options: {
credentialsRole: restApiRole,
passthroughBehavior: apigateway.PassthroughBehavior.WHEN_NO_MATCH,
requestParameters: {
// メソッドリクエストのパスパラメータ userId を 統合リクエストのパスパラメータ folder にマッピングする
'integration.request.path.folder': 'method.request.path.userId',
// メソッドリクエストのパスパラメータ fileName を 統合リクエストの object にマッピングする
'integration.request.path.object': 'method.request.path.fileName',
},
integrationResponses: [
{
statusCode: '200',
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',
'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': "'*'",
},
},
{
statusCode: '400',
selectionPattern: '4\\d{2}',
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': "'*'",
},
},
{
statusCode: '500',
selectionPattern: '5\\d{2}',
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': "'*'",
},
},
],
},
}),
{
requestParameters: {
'method.request.path.userId': true,
'method.request.path.fileName': true,
},
methodResponses: [
{
statusCode: '200',
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,
},
},
{
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,
},
},
{
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,
},
},
],
}
);
}
}
動作確認(テキストファイル)
テキトーなテキストファイルを作成して、直接 S3 にアップロードします。その後、API Gateway で作成した REST API にリクエストすると、アップロードしたファイルの中身が返ってきます。
$ cat sample.txt
hello, world
$ aws s3 cp sample.txt s3://<BUCKET_NAME>/sample-user/sample.txt
upload: ./sample.txt to s3://<BUCKET_NAME>/sample-user/sample.txt
$ curl https://<DOMAIN_NAME>/v1/users/sample-user/files/sample.txt
hello, world
テキストファイルをダウンロードするのであればこれで良いのですが、このままだと画像のダウンロードは上手くいきません。少し API の設定を変更する必要があります。
画像のダウンロードに対応する
設定変更箇所をハイライト表示しています。
lib/api-gateway-proxy-to-s3-by-cdk-stack.ts
import * as cdk from '@aws-cdk/core';
import * as apigateway from '@aws-cdk/aws-apigateway';
import * as iam from '@aws-cdk/aws-iam';
import * as s3 from '@aws-cdk/aws-s3';
export class ApiGatewayProxyToS3ByCdkStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const projectName: string = this.node.tryGetContext('projectName');
// ★ S3
const bucket = new s3.Bucket(this, 'Bucket', {
bucketName: `${projectName}-bucket`,
});
// ★ API Gateway
const restApiRole = new iam.Role(this, 'Role', {
assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'),
path: '/',
});
bucket.grantRead(restApiRole);
const restApi = new apigateway.RestApi(this, 'RestApi', {
restApiName: `${projectName}-api`,
deployOptions: {
stageName: 'v1',
loggingLevel: apigateway.MethodLoggingLevel.INFO,
dataTraceEnabled: true,
},
defaultCorsPreflightOptions: {
allowOrigins: apigateway.Cors.ALL_ORIGINS,
allowMethods: ['POST', 'OPTIONS', 'PUT', 'DELETE'],
statusCode: 200,
},
// バイナリメディアタイプを設定して、画像を扱えるようする
binaryMediaTypes: ['image/*']
});
// リソースを作成する `/users/{userId}/files/{fileName}`
const users = restApi.root.addResource('users');
const userId = users.addResource('{userId}');
const files = userId.addResource('files');
const fileName = files.addResource('{fileName}');
// オブジェクトをダウンロードするための GET メソッドを作成する
fileName.addMethod(
'GET',
new apigateway.AwsIntegration({
service: 's3',
integrationHttpMethod: 'GET',
// ダウンロード先を指定する
path: `${bucket.bucketName}/{folder}/{object}`,
options: {
credentialsRole: restApiRole,
passthroughBehavior: apigateway.PassthroughBehavior.WHEN_NO_MATCH,
requestParameters: {
// メソッドリクエストのヘッダー Accept を 統合リクエストのヘッダーにマッピングする
'integration.request.header.Accept': 'method.request.header.Accept',
// メソッドリクエストのパスパラメータ userId を 統合リクエストのパスパラメータ folder にマッピングする
'integration.request.path.folder': 'method.request.path.userId',
// メソッドリクエストのパスパラメータ fileName を 統合リクエストの object にマッピングする
'integration.request.path.object': 'method.request.path.fileName',
},
integrationResponses: [
{
statusCode: '200',
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',
'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': "'*'",
},
},
{
statusCode: '400',
selectionPattern: '4\\d{2}',
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': "'*'",
},
},
{
statusCode: '500',
selectionPattern: '5\\d{2}',
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': "'*'",
},
},
],
},
}),
{
requestParameters: {
'method.request.header.Accept': true,
'method.request.path.userId': true,
'method.request.path.fileName': true,
},
methodResponses: [
{
statusCode: '200',
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,
},
},
{
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,
},
},
{
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,
},
},
],
}
);
}
}
動作確認(画像)
動作確認用に、この画像をsample.png
として使用します。
画像を直接 S3 にアップロードした後、API Gateway で作成した REST API にリクエストすると、アップロードした画像をダウンロードできます。
$ aws s3 cp sample.png s3://<BUCKET_NAME>/sample-user/sample.png
upload: ./sample.png to s3://<BUCKET_NAME>/sample-user/sample.png
$ curl https://<DOMAIN_NAME>/v1/users/sample-user/files/sample.png --output get-sample.png
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 68853 100 68853 0 0 294k 0 --:--:-- --:--:-- --:--:-- 294k
html ファイルの img タグで画像を表示した場合も上手くいきます。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Sample</title>
</head>
<body>
<p>GET Image File</p>
<img
src="https://<DOMAIN_NAME>/v1/users/sample-user/files/sample.png"
/>
</body>
</html>
おわりに
本記事では、API Gateway で S3 をプロキシして、Bucket に格納されているオブジェクトをダウンロードしてみました。次回は、オブジェクトのアップロードにチャレンジします。
今回は以上になります。最後まで読んで頂きありがとうございました!