以下の流れで実装しました。
- PostObjectV4 のインスタンスを使い、署名付きPOSTに必要な情報を発行
- Vue.jsで、アプリケーションサーバを介さず、クライアント側から直接S3にファイルをアップロード
準備
AWS
S3でバケットを作成してください。
※IAMの設定は省略します
S3 CORSの設定
CORSを設定しておかなければなりません。
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
AWS-SDK-PHPのインストール
$ composer require aws/aws-sdk-php
実装
署名付きPOSTに必要なデータを発行するPHPの処理と、ファイルアップロードするVue.jsの処理を記載します。
実装した処理は以下の通りです。
- 署名付きPOSTに必要なデータ形式のjsonを返すAPI
- Vue.jsから↑のAPIを叩き、レスポンスを取得
- レスポンスを利用し、ファイルアップロード
API(PHP)
<?php
// laravel apiのcontrollerの処理を一部抜粋
public function getPresignedUrl(Request $request)
{
$s3Client = new \Aws\S3\S3Client([
'credentials' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
],
'region' => env('AWS_DEFAULT_REGION'),
'version' => 'latest'
]);
$bucket = env('AWS_BUCKET');
$requestData = $request->all();
$formInputs = [
'acl' => 'public-read',
'key' => 'hoge/' . $requestData['filename'] . '.' . $requestData['fileext'],
];
$options = [
['acl' => 'public-read'],
['bucket' => $bucket],
['starts-with', '$key', 'hoge/'],
];
$expires = '+20 minutes';
$postObject = new \Aws\S3\PostObjectV4(
$s3Client,
$bucket,
$formInputs,
$options,
$expires
);
$formAttributes = $postObject->getFormAttributes();
$formInputs = $postObject->getFormInputs();
return response()
->json([
'url' => $formAttributes['action'],
'fields' => $formInputs
]);
}
Client側(Vue.js)
<script>
export default {
name: 'AwsS3Upload',
methods: {
async upload() {
const upload_files = document.getElementById('upload-file');
const upload_file = upload_files.files[0];
// 署名付きPOSTのAPI叩く
let preSignedUrl = await this.getPresignedUrl();
// S3へアップロード
let uploadS3Path = await this.uploadS3(preSignedUrl, upload_file);
},
async getPresignedUrl() {
// ↓ここのファイル名は仮置きで適当になってますw
let filename = 'fuga';
let filetype = 'image/jpeg'
let fileext = 'jpg'
try {
const url = '/api/get-presigned-url?filename=' + filename + '&filetype=' + filetype + '&fileext=' + fileext;
let response = await axios.get(url);
console.log('S3署名付きURL取得 成功');
return response;
} catch (error) {
console.log('S3 署名付きURL取得 失敗');
}
},
async uploadS3(presignedUrl, up_file) {
let data = presignedUrl.data;
try {
var formdata = new FormData();
for (let key in data.fields) {
formdata.append(key, data.fields[key]);
}
formdata.append("file", up_file);
const headers = {
"content-type": "multipart/form-data",
}
console.log('S3 アップロード 開始');
let response = await axios.post(
data.url,
formdata,
{
headers: headers,
}
);
console.log('S3 アップロード 成功');
return data.url + '/' + data.fields.key;
} catch (error) {
console.log('S3 アップロード エラー');
}
},
}
}
</script>
結論
署名付きPOSTで、S3へ直接クライアントサイドからファイルアップロードができました。
また、アプリケーションサーバを介さずにファイルをアップロードできるため、ファイルサイズが大きい場合においてもサーバに負荷をかけずに済みます。 (20191129追記)
参考
【AWS S3】S3 Presigned URLの仕組みを調べてみた
CORS(Cross-Origin Resource Sharing)について整理してみた
PresignedPost.php
ブラウザからS3へのダイレクトアップロード