書籍『AWSによるサーバーレスアーキテクチャ』(翔泳社)を読み進めているときにS3のファイルアップロード関連でつまづいたところが多々あったので、気をつけるべきところとして手順と一緒にまとめます。
AWSに関わる動的な処理を全てJavaScriptで実装することを選択した場合、AWS Lambda上ではAWS SDK for JavaScript一択ではありますが、ブラウザの場合は生のJavaScriptで記述することも可能です。
今回はブラウザ側で実行されるS3関連のコードをSDKを使わずREST APIで扱った時のメモです。 (書籍に従って生JSでゴリゴリ書きましたけど、潔くSDKを使えばこういったところで躓かなかったのではと思わずにはいられない。)
やること
署名付きURLを発行する
AWS S3 署名バージョン4でリクエストする
AWS regions created before January 30, 2014 will continue to support the previous protocol, Signature Version 2. Any new regions after January 30, 2014 will support only Signature Version 4 and therefore all requests to those regions must be made with Signature Version 4
https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/API/sig-v4-authenticating-requests.html
以下、コード例。
"use strict"; const AWS = require("aws-sdk"); const S3 = new AWS.S3({ signatureVersion: 'v4' }); const crypto = require("crypto"); function getSignedUrl(fileName) { const prefix = crypto.randomBytes(20).toString("hex"); const params = { Bucket: process.env.UPLOAD_BUCKET, Key: prefix + "/" + fileName, Expires: 600 }; return S3.getSignedUrl("putObject", params); } module.exports = { getSignedUrl };
オペレーション名を一緒に付与すること
- オペレーションを指定するときは、
putObject
とキャメルケースで指定すること。PutObject
と先頭大文字で指定するとエラーで弾かれる。 - 公式ドキュメントには上記は明記されていないが、Sampleにはちゃっかりキャメルケースで指定されている。
- オペレーションを指定するときは、
ここで返却される署名付きURLは仮想ホスト形式で返されます。
http://{bucket-name}.s3.amazonaws.com
http://{bucket-name}.s3-aws-region.amazonaws.com
バケットのアクセス権限を設定する
バケットポリシー
まずはパブリックアクセス権限が付いていたら外します。 今回は署名付きURLでダウンロード/アップロードを行うため、リクエストの度にリクエスト者に権限が付与される仕組みにするためです。
バケットを選択 -> アクセス権限 -> バケットポリシーを開いてドキュメントを削除する。バケット一覧で該当のバケットに"非公開"と記載されていればOK。
常に最小限の権限を付与することが推奨されているため、不用意にパブリックアクセスを許可することは回避した方が良いです。
CORS設定
AWSとは別ドメインからアクセスすることになるので、どこから/どんなオペレーションが許可されるかバケットに設定する必要があります。 バケットを選択 -> アクセス権限 -> CORSの設定から以下のように設定を追加します。これだけ。
<?xml version="1.0" encoding="UTF-8"?> <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <CORSRule> <AllowedOrigin>*</AllowedOrigin> <AllowedMethod>POST</AllowedMethod> <AllowedMethod>PUT</AllowedMethod> <MaxAgeSeconds>3000</MaxAgeSeconds> <AllowedHeader>*</AllowedHeader> </CORSRule> </CORSConfiguration>
あと地味なTipsとしては、1行目の<?xml...
の記載は設定のエディタに書かなくても勝手に追加されます。
REST API経由でリクエストを投げる
PUTリクエストで投げる。
オブジェクトの新規作成をするんだからPOSTリクエストじゃね?と直感的に思いつきますが、PUTリクエストで投げます。POSTメソッドでリクエストを投げてもAccess Deniedと403が寂しく返ってくるだけです。
思い返してみれば、オペレーション名はPutObject
だもんね。
おそらく同一キーのオブジェクトに対しては上書きもできることから、POSTメソッドではなくPUTメソッドのリクエストにしているのではと推測します。 --> REST API Reference: PUT Object
Content-Typeはファイルのものを指定する
画像や動画をHTMLから送信する場合の定石だとContent-Type: multipart/form-data
でデータをバックエンドに送信しますが、S3へのアップロードに関しては単一ファイルの場合はmultipartリクエストを行うのは間違いです。
ファイル本体のContent-Typeを指定しましょう。
例えば、MP4の動画をアップロードする場合は、Content-Type: video/mp4
といった具合です。
"use strict"; /** * ファイルをS3バケットにアップロードする。 * @param {string} url アップロード先S3バケットの署名付きURL */ function upload(url) { // MP4の動画を添付したと想定。 const uploadBtn = document.getElementById("uploadButton"); const file = uploadButton.files[0]; // S3バケットにアップロード実行。 $.ajax({ url: url, type: "PUT", data: file, processData: false, contentType: "video/mp4" }) .done(response => { console.log(response); alert("Upload Finished!"); }) .fail(err => { console.log(err); alert("Failed to upload..."); }); }
実はこれもちゃっかり公式ドキュメントに記載されています。日本語版にはないですけどね。 以下のExample2にJPEG画像の例が参考になります。 https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/API/RESTObjectPUT.html?shortFooter=true#RESTObjectPUT-responses-examples
ブラウザに含めるJSファイルのサイズを気にしてSDKを入れられない場合などに参考にしていただければと思います。
以上。