CloudFront と S3 で 配信した Angular 製 SPA から API Gateway を通してファイルアップロードする
サーバーレスな話で以下のような構成、よく見かけます。
- CloudFront と S3 で SPA を配信する
- API Gateway と S3 で CRUD API を作る
よく見かけるのですが、私が知っているのは「構成」であって「実装」ではないな、ということで簡単なものを作ってみることにしました。図にすると以下のようなイメージ。
簡単なストーリー
- 各地域に投票所があり、エンドユーザーが投票する
- 地域ごとに投票データをCSV管理しており、投票が終わったら管理者が中央ストレージへCSVファイルをアップロードする
想定読者
サーバーレス構成で CloudFront や S3 を組み合わせたサンプルを見てみたい方。
作ってみて
最初に感想を書いてしまいます。
- CloudFront と S3 の組み合わせは強力。ドメインを持っていたら迷わず横綱構成へ持っていくべき。
- API Gateway は思った以上にいろいろな AWS サービス と組み合わせられる。運用を考えると AWS CLI でAPI構成を変更管理できる状態が望ましい。今回はAWSコンソールで作成した。
- 10年前の私がファイルアップロードシステムを作ろうと思ったらまずアップロード先のストレージシステムを選定してサーバーサイドは Tomcat で作ってフロントエンドはJSP触るの嫌だから別のテンプレートエンジンを…とか考えるところからスタートしてたと思います。いい時代になった。
フロント側 = Angular4 + S3 + CloudFront
Angular製SPAを作る
可能な限り省力化します。今回は認証も考慮しません。その前提で、 ng2-file-upload を使いました。ファイルアップロードの高レベルAPIを提供してくれるディレクティブで、デモページを参考にすれば簡単にファイルアップロードページを実装できます。
デモページを参考に、以下のようなページを実装しました。
コードサンプルは GitHub にアップしています。実際に利用する場合は、ソースコード内、アップロード先のURLを調整してください。
$ git clone git@github.com:cm-wada-yusuke/vote.git
import {Component, OnInit} from '@angular/core'; import {FileItem, FileUploader} from 'ng2-file-upload'; import {FormControl, Validators} from '@angular/forms'; // const URL = '/api/'; const URL = 'https://your.file.upload.server.com/v1/vote_uploader'; @Component({ selector: 'app-file-upload', templateUrl: './file-upload.component.html', styleUrls: ['./file-upload.component.css'] }) export class FileUploadComponent implements OnInit { // 後略 ...
ソースコードをビルドします。
$ cd path/to/vote-angular-project $ ng build --prod
これで、Angular SPA の準備は完了です。早い…!
SPA配信用 S3 を用意する
簡単のために、今回は、配信元とCSVファイルアップロード先を同一バケットにしました。
vote-uploader(bucket) ├── site # SPA 配信元 └── uploads # CSV アップロード先(地域ごと) ├── chiyoda ├── sumida ├── ...
先で作った SPA ファイルを S3 にアップロードします。
$ cd dist/ $ aws s3 sync . s3://vote-uploader/site
SPAアップロード先の S3 は以下のような画面になります。デプロイ完了。
CloudFront をたてる
Origins を以下のように設定します。
Origin Path を /site
とすることによって、ドキュメントルートの階層を変更することが可能です。
Behavior を以下の様に設定します。
この設定で保存し、しばらくまちます。その後、https://yourcloudfrontsubdomain.cloudfront.net/index.html
へアクセスすると、アップロードした SPA へアクセスすることができます。これでフロント側は終わりです。
アップロードAPI側 = API Gateway + S3
今回はファイルアップロード機能のみ作ります。API Gateway を通して、S3 上の /vote-uploader/uploads/{地域フォルダ}
へアップロードすることが目標です。いくつかポイントがあります。
- S3をバックエンドとしたAPIを定義する
- パスパラメータとクエリパラメータをマッピングする
- CORS を許可する
APIの定義
まずは API Gateway のリソースを作りましょう。アクション>リソースの作成 を行って以下のように作成します。
次に、PUTメソッドを作成します。バックエンドをS3とする関係上、API Gateway から S3 へのアクセスが許可されている必要があります。これを IAMポリシーのアタッチ により実現します。AWSコンソールで、API Gateway のロールを作成しましょう。
API Gateway の画面に戻り、作成したリソースに対してPUTメソッドを定義します。
パスの上書き について。このような書き方をすると、
- 使われるバケット: vote-uploader
- キー: uploads/{placeId}
- オブジェクト名: {key}
という意味になります。{placeId}と{key}は、動的な値が入ることを意味します。API Gateway で受け付けたパスパラメータやクエリパラメータを、S3 へアクセスするためのパラメータに変換します。これが パスのマッピング です。
パスのマッピング
さて、PUTメソッドを作成すると、メソッドのリクエスト/レスポンスについて設定できるようになります。
②③の設定で、API Gateway が https"//xxx/vote_uploader/chiyoda?fileName=2017-10-31.csv
といったパスを解釈してくれるようになります。②により{placeId}
にはchiyoda
が、③により{fileName}
には2017-10-31.csv
が入ります。次にこれらを S3 のパスにマッピングします。
- ⑤: API Gateway のクエリパラメータ
fileName
を、S3パスkey
にマッピングします - ⑥: API Gateway のパスパラメータ
placeId
を、S3パスplaceId
にマッピングします
結果、https://xxx/vote_uploader/chiyoda?fileName=2017-10-31.csv
というアクセスに対しては、
- 使われるバケット: vote-uploader
- キー: uploads/chiyoda
- オブジェクト名: 2017-10-31.csv
と解釈されます。
CORSの許可
SPAのドメインと API Gateway のドメインは異なるため、API Gateway 側でCORSを許可する必要があります。API Gateway 管理画面、アクション>CORSの有効化 で、以下のように設定してください。
準備完了です。デプロイします。デプロイした後に取得できるURLが、ファイルアップロード先のURLになります。
試す
フロントとサーバーどちらも用意できました。早速使ってみましょう。
Vote region は chiyoda
、ファイル名は 2017-10-31.csv
であることがわかります。このとき、Angularで、アップロードURL https://XXXXXX.execute-api.ap-northeast-1.amazonaws.com/v1/vote_uploader/chiyoda?2017-10-31.csv
が生成されるよう実装しています。アップロードされたはずですので S3 を見てみましょう。
ファイルがアップロードできました。
おわりに
SPAのホスティングと、ファイルアップロード先としてS3を使うことができました。S3は高可用なオンラインストレージという印象が強いですが、それだけでなく CloudFront や API Gateway と組み合わせることで、思った以上に便利だとわかりました。「静的なコンテンツを保存し、配信する」という要件に出くわしたときは、まずS3をベースにできるかどうか、検討すると良いでしょう。
今回は投票ファイルをアップロードするところまででしたが、その先、集計業務もセットで必要になることが多いと思います。集計についてもAWSを使ってやってみたいと思います。