はじめに
Amazon API Gateway の AWS サービス統合では、Lambda を通さずに直接サービスにアクセスすることができて、No-Code、Low-Code な感じでサービスを構築することができる(実際には YAML とか VTL とか知らないといけないから、No-Code ではないか)。
今回は、S3バケットにファイルをアップロードする API Gateway を構築してみる。
なお、参考にしたのは以下の公式ドキュメントだ。
Amazon API Gatewayの構築
アップロードする API は API Gateway の prod/uploader
に PUT するメソッドを作ることにしよう。
logformat.json はあってもなくても良いが、サービス統合するときには、ログをちゃんと出しておかないと、トラブったときに何もわからなくなるので、出しておくことを推奨する。
API Gateway を構築する HCL ファイルの全体
resource "aws_api_gateway_rest_api" "uploader" {
name = local.api_gateway_name
description = "S3アップロードAPI用API Gateway"
binary_media_types = [
"application/octet-stream",
]
}
resource "aws_api_gateway_account" "my_api" {
cloudwatch_role_arn = aws_iam_role.apigateway_putlog.arn
}
resource "aws_api_gateway_stage" "prod" {
stage_name = "prod"
rest_api_id = aws_api_gateway_rest_api.uploader.id
deployment_id = aws_api_gateway_deployment.for_prod.id
access_log_settings {
destination_arn = aws_cloudwatch_log_group.apigateway_accesslog.arn
format = file("${path.module}/logformat.json")
}
}
resource "aws_api_gateway_deployment" "for_prod" {
rest_api_id = aws_api_gateway_rest_api.uploader.id
triggers = {
redeployment = sha1(join(",", list(
jsonencode(aws_api_gateway_rest_api.uploader),
jsonencode(aws_api_gateway_integration.uploader_put_s3),
)))
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_api_gateway_method_settings" "all" {
depends_on = [
aws_api_gateway_account.my_api,
]
rest_api_id = aws_api_gateway_rest_api.uploader.id
stage_name = aws_api_gateway_stage.prod.stage_name
method_path = "*/*"
settings {
logging_level = "INFO"
data_trace_enabled = true
}
}
resource "aws_api_gateway_request_validator" "uploader" {
name = "Validator"
rest_api_id = aws_api_gateway_rest_api.uploader.id
validate_request_body = true
validate_request_parameters = false
}
resource "aws_api_gateway_resource" "uploader" {
rest_api_id = aws_api_gateway_rest_api.uploader.id
parent_id = aws_api_gateway_rest_api.uploader.root_resource_id
path_part = "uploader"
}
resource "aws_api_gateway_method" "uploader_put" {
rest_api_id = aws_api_gateway_rest_api.uploader.id
resource_id = aws_api_gateway_resource.uploader.id
http_method = "PUT"
authorization = "NONE"
}
resource "aws_api_gateway_method_response" "uploader_put_200" {
rest_api_id = aws_api_gateway_rest_api.uploader.id
resource_id = aws_api_gateway_resource.uploader.id
http_method = aws_api_gateway_method.uploader_put.http_method
status_code = "200"
}
resource "aws_api_gateway_integration" "uploader_put_s3" {
rest_api_id = aws_api_gateway_rest_api.uploader.id
resource_id = aws_api_gateway_resource.uploader.id
http_method = aws_api_gateway_method.uploader_put.http_method
type = "AWS"
uri = "arn:aws:apigateway:${data.aws_region.current.name}:s3:path/${aws_s3_bucket.uploader.bucket}/test_object"
integration_http_method = "PUT"
credentials = aws_iam_role.apigateway_s3_access.arn
passthrough_behavior = "WHEN_NO_MATCH"
content_handling = "CONVERT_TO_BINARY"
}
アップロード先になる S3 バケットは以下のように作っておく。
これも当然ながら、アクセス制御は API Gateway 側で実施するので、バケットの設定は private で問題ない。
resource "aws_s3_bucket" "uploader" {
bucket = local.s3_uploader_bucket_name
acl = "private"
}
また、IAM は CloudWatchLogs にアクセスできるよう、aws_iam_role.apigateway_putlog.arn
に AmazonAPIGatewayPushToCloudWatchLogs
のマネージドポリシーをアタッチしておこう。
ここで肝になるのが、aws_api_gateway_rest_api.uploader
の
binary_media_types = [
"application/octet-stream",
]
の部分と、aws_api_gateway_integration.uploader_put_s3
の
passthrough_behavior = "WHEN_NO_MATCH"
content_handling = "CONVERT_TO_BINARY"
の部分だ。上記の参考ドキュメントでの方式では、content-type: application/octet-stream
のアップロードをしているため、以下の公式ドキュメントの
に書いてあるのに従い、今回は以下の通りのアップロード使用を考えているため、上記の通りの設定を行う。
メソッドリクエストペイロード | リクエスト Content-Type ヘッダー | binaryMediaTypes | contentHandling | 統合リクエストペイロード |
---|---|---|---|---|
バイナリデータ | バイナリデータ型 | 一致するメディアタイプで設定 | CONVERT_TO_BINARY | バイナリデータ |
aws_api_gateway_integration.uploader_put_s3
は、API Gateway のサービス統合が S3 バケットにアクセスできるよう、以下の通りのロールを作っておく。
resource "aws_iam_role" "apigateway_s3_access" {
name = local.rest_api_s3_access_role_name
assume_role_policy = data.aws_iam_policy_document.apigateway_s3_access_assume.json
}
data "aws_iam_policy_document" "apigateway_s3_access_assume" {
statement {
effect = "Allow"
actions = [
"sts:AssumeRole",
]
principals {
type = "Service"
identifiers = [
"apigateway.amazonaws.com",
]
}
}
}
resource "aws_iam_role_policy_attachment" "apigateway_s3_access" {
role = aws_iam_role.apigateway_s3_access.name
policy_arn = aws_iam_policy.apigateway_s3_access_custom.arn
}
resource "aws_iam_policy" "apigateway_s3_access_custom" {
name = local.rest_api_s3_access_policy_name
policy = data.aws_iam_policy_document.apigateway_s3_access_custom.json
}
data "aws_iam_policy_document" "apigateway_s3_access_custom" {
statement {
effect = "Allow"
actions = [
"s3:PutObject",
]
resources = [
"${aws_s3_bucket.uploader.arn}/*",
]
}
}
さて、これで API Gateway は準備完了だ。
ファイルアップロードをする Javascript
シンプルに以下のように作成する。
コンテンツは、Nginx なり S3 の静的Webサイトホスティングなりに入れておけば良いだろう。
${apigateway_invoke_url}
の部分は適宜置換を行う。
静的Webサイトホスティングについては以下の記事を参照。
- [S3で色々お試しして遊んでみる(VPCエンドポイント&静的Webホスティング性能編)]
(https://qiita.com/neruneruo/items/d6e8776d3599f483fc55)
また、上記の API Gateway はかなり雑につくっているので、このようにファイルアップロードする Javascript を作る場合は、当然ながら CORS の設定が必要になる。
CORS も Amazon API Gateway であれば、サクッと対応することができる。詳細は以下の記事を参照。
<html>
<head>
<style>
[v-cloak] { display: none }
</style>
<meta charset="utf-8">
<title>S3アップローダ</title>
</head>
<body>
<div id="app">
<h1>S3アップローダ</h1>
<input v-on:change="selectedFile" type="file" name="file">
<button v-on:click="upload" type="submit">アップロード</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.js"></script>
<script src="app.js"></script>
</body>
</html>
const app = new Vue({
el: '#app',
data: {
uploadFile: null
},
methods: {
selectedFile: function (e) {
e.preventDefault()
const files = e.target.files
this.uploadFile = files[0]
},
upload: function () {
var fileReader = new FileReader()
fileReader.addEventListener('load', function (e) {
const config = {
url: '${apigateway_invoke_url}/uploader',
method: 'put',
headers: {
'content-type': 'application/octet-stream',
accept: 'application/json'
},
data: e.target.result
}
axios
.request(config)
.then(function (response) {
console.log(response)
})
.catch(function (error) {
console.log(error)
})
})
fileReader.readAsArrayBuffer(this.uploadFile)
}
}
})
app.$mount('#app')
これで、index.html のコンテンツからファイルアップロードすると、S3バケットにそのコンテンツが格納される。
バイナリ格納しているので、画像でも何でも渡せるはずだ!