Help us understand the problem. What is going on with this article?

[AWS SDK for PHP]署名付きPOSTを使い、直接クライアント側からAWS S3にファイルアップロード(PostObjectV4インスタンスを使用)

以下の流れで実装しました。

  • PostObjectV4 のインスタンスを使い、署名付きPOSTに必要な情報を発行
  • Vue.jsで、アプリケーションサーバを介さず、クライアント側から直接S3にファイルをアップロード

画面はこんな感じで、簡単にアップロード画面を作ってます。
スクリーンショット 2019-11-18 23.51.42.png

準備

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へのダイレクトアップロード

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away