RareJob Tech Blog

レアジョブのエンジニア・デザイナーによる技術ブログです

Amazon S3にファイルをフロントエンドから直接アップロードしたかった話

バックエンドエンジニアです。 涼しくなってきましたが、みなさんファッション好きですか? 私は好きです。zozotownに掲載して頂きました。

今回やりたかった事

とある業務で500MB以上の動画ファイルをシステムに保存し、参照できるようにしたいという事でした。

前提

・S3にアクセスするアクセスキーとシークレットキーはセキュアにする為、Backendに持っている。
・S3には限定的なアクセス権を持たせて特定のディレクトリにアクセスしたい。

課題

原始的にバックエンド経由でアップロードする実装にしたところ、メモリ不足のエラーが出てしまった。

解決策

Amazon S3にはPre-Signed URLという機能があり、これを使うと期限付きで直接JavaScriptからS3へアップロード出来る事がわかったので、 上記課題を解決できると判断し、使ってみました。 バッファリングに仕様を変更する改修やCognitoを用いて一時的な認証方法を付与する方法も検討しましたが、今回はPre-Signed URLを使ったほうがさくっと対応出来そうなのでこちらを採用しました。

処理フロー

f:id:daichangdesu:20190913180026p:plain ざっくりとこんな感じのフローを考えました。

①request Pre-signed URL to Backend

JavaScriptからBackendにアップロード必要な情報をAPIで要求します。

②request Pre-signed URL to AWS

BackendからAWSAccess KeyとSecret Keyを使ってPre-signed URLをAWSに要求します。 ※Laravel5.5、league/flysystem-aws-s3-v3を使用したサンプルコード

public function getPreSignedUrl(string $filePath): string
{
    $s3 = Storage::disk('s3_private');
    $client = $s3->getDriver()->getAdapter()->getClient();
    $expiry = "+30 minutes";

    $command = $client->getCommand('PutObject', [
        'Bucket' => Config::get('filesystems.disks.s3_private.bucket'),
        'Key' => $filePath,
        'ACL' => 'public-read'
    ]);

    $request = $client->createPresignedRequest($command, $expiry);

    return (string) $request->getUri();
}

③return Pre-signed URL from AWS

AWSからPre-signed URLを取得します。

④return Pre-signed URL from Backend

取得したURLをJavaScriptに返します。

⑤upload file to AWS

JavaScriptから取得したURLを使ってアップロードします。

考察

結果問題なくアップロード出来るようになりました。
実装時にAWSから取得する所まではすんなり行ったのですが、フロントから動画ファイルをアップロード するときにヘッダーの書き方によってはアップロードが完了してもファイルが壊れてしまっていたりしてとても焦りました。
今回はaxiosを使って以下のようにアップロードする事により実現しました。

return axios.put(preSignedUrl, this.videoDataBinary, {
    headers: {
        'Content-Type': 'video/mp4'
},

シンプルにこれだけで行けました.... 今回の学びは今後アプリからファイルをアップロードしたりする時等に使えそうだなと思いました。