S3のPutObjectを一時的に使用可能なURLとして公開する必要があり、サーバ側のロジックより一時URLの取得ができたので、一時URLをブラウザに返却、ブラウザから一時URLにPUTリクエストした際に直面した問題についてメモ。
#HTTP通信に詳しい方は聞き流して頂きたい
(ちなみにこのような仕様になっているのは、大容量のファイルをS3へ素早く登録するため。サーバからS3へPUTする場合はPUT対象のファイルをブラウザ→サーバ、サーバ→S3へ転送することになり処理時間がブラウザ→サーバへの転送分多くなる。また、アプリサーバやサーバ側フレームワークに最大ファイルサイズ設定があり、この最大ファイルサイズ設定を都度変更する必要があり、運用上この設定を頻繁に変更したくないとの要望があっため)
一時的に使用可能なURL
まず、一時的に使用可能なURLについて。
今回はPHPのSDKを使用したため、以下のページを参照。
https://docs.aws.amazon.com/ja_jp/sdk-for-php/v3/developer-guide/s3-presigned-url.html
上記の内容に沿って実装すれば簡単に署名付きURLを作成可能。
上記のサンプルではGetObjectだが、コマンドの部分をPutObjectに変えるだけでPutObjectの署名付きURLを作成可能。
KeyにはバケットにPUTするファイル名を指定すればOK。(既にキーが存在する場合は上書きされるので注意)
ブラウザからリクエスト!
話は戻って、サーバから返却された一時URLをブラウザから呼び出す。
javaScriptは、jQueryでajaxでリクエスト送信した。
$.ajax({
url: [一時URL],
type: 'PUT',
contentType : false,
cache: false,
processData: false,
data: [PUTするファイル],
success: function(json) {
console.log("success");
},
error: function(xhr, status, error) {
console.log("error");
},
complete: function(xhr, status) {
console.log("done");
},
});
結果、
jquery.min.js:4 OPTIONS [一時URL] 403 (Forbidden)
Failed to load [一時URL]: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin [リクエスト元のオリジン] is therefore not allowed access.
何故このような結果になったのか??
プリフライトリクエスト
エラー内容を見るとPUTリクエストではなくOPTIONSリクエストが送信された際のエラーとなっている。
HTTP通信に詳しい方はご存知かと思うが、オリジン (ドメイン、プロトコル、ポート番号) の異なるサイトへのPUTリクエストを送信する際、プリフライトリクエストなるリクエストが発行される。
(詳しくは→https://developer.mozilla.org/ja/docs/Web/HTTP/HTTP_access_control#Preflighted_requests)
プリフライトリクエストはオリジン間リソース共有 (CORS)の仕組みにのっとった手法で、AWSにもCORSへの対応が記載されている。
(https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/cors.html)
上記を踏まえるとエラー内容は、「プリフライトリクエストのOPTIONSリクエストを送信した際に、送信先オリジンがリクエスト元のオリジンのリクエストを許可していなかったため」、と読むことができる。
再度リクエスト!
前述のプリフライトリクエストが許可されるようにS3のバケットのCORSConfigurationのAllowedOrigin設定にリクエスト元のオリジンを、AllowedMethodにPUTを設定。
再度リクエストを送信!!!
結果、無事にS3へのPUTリクエストが完了した。
まとめ
- 別オリジンへの一部のHTTP通信はプリフライトリクエストが発行される
- AWS S3の設定でオリジン間リソース共有 (CORS)に対応可能
- S3へのリクエストでGET以外のリクエストがエラーとなりリクエストメソッドがOPTIONSであった場合はS3のCORS設定を見直そう