S3にファイルをアップロード機能をサーバレスで実現しようと思い、API Gatewayを使えば楽にできるだろうと思っていたらかなりハマりました。おそらく、他にも方法はあるでしょうが、今回は次のような方法で解決することができました。
やりたいこと
今回、やりたいことは、Reactで実装されたUIからAPI Gatewayを経由してHTTP POSTでファイルをアップロードした際に、S3の適切な場所にファイルをアップロードできるようにすることです。
サービス構成と処理手順
今回はこのような構成と手順でサービスを構築しました。
- ユーザはブラウザでReactで構築されたサイトにアクセスする。
- Reactで構築されたサイトのフォームからファイルをアップロードする。
- Reactでbase64変換してHTTP POSTでAPI Gatwayに送る。
- API GatewayにつながったLambdaにPOSTされたデータを渡す。
- Lambdaでbase64からバイナリに変換する。
- S3の適切なバケットへファイルがPUTする。
実現手順
- Reactでファイルをアップロードする機能を実装する
- IAMで適切なアクセス権を持つユーザ、ロールを作成する
- S3でファイルをアップロードするバケットを作成する
- AWS Lambdaでファイルを受け取ってS3にアップロードする機能を実装する
- API GatewayとLambda関数を関連付けて公開する
かなりボリュームが多くなったため、これらそれぞれを別々の投稿としました。ここでは、これを実現する上ではまったポイントだけを書こうと思います。
はまったポイント
axiosでファイルをPOSTするときに、POSTするパラメータを格納するために、FormDataオブジェクトを利用したことが原因でエラーが発生していました。これが、Content-Type ErrorやBodyの形式が問題があるなど、正確なエラーメッセージを返してくれればもう少し早く解決したのでしょうが、今回は、「No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://xxxxx’ is therefore not allowed access. The response had HTTP status code 400.」と、クロスドメインのエラーとして返されたため、数日はまることになってしまいました。
最終的には、FormDataを利用せず、Base64変換してJSONのvalueに代入してPOSTすることで、今回は解決させました。
しかし、他のサイトを見てみると、バイナリのままPOSTしたり、さらにはLambdaを介さなくて、直接、API GatewayからS3へPUTする方法も書かれていたので、他にもよりスマートな解決方法がありそうです。
関連ページ
- Reactでファイルをアップロードするサイトの構築