23
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ブラウザからS3へのダイレクトアップロード

Last updated at Posted at 2019-06-07

はじめまして、streampackチームのminsuです。

やりたいこと

S3へファイルをアップロードする際に、Railsサーバを通すことなくブラウザからS3へのダイレクトアップロードを実装してみます。
ブラウザからS3にファイルを直接アップロードすることにより、余分な負荷を削減できるメリットがあります。
また、Railsのgem aws-sdkを利用して生成したpresigned POSTを利用することでブラウザにaws credentialsを持たせる事なくアップロードを行えます。

AWSリソースの準備

まず、AWSアクセスキーを作成してACCESS_KEY_ID, SECRET_ACCESS_KEYを取得してください。

次にS3のバケットの作成します。
作成したバケットのCORSの設定を行い、外部からのPOSTを許可します。

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
  </CORSRule>
</CORSConfiguration>

AllowedOrigin、AllowedHeaderはワイルドカードを設定しましたが、環境に合わせて変更してください。

Railsでpresigned POSTを返すアクションを設定

まずは環境変数に必要な値を持たせておきます。

.env
AWS_ACCESS_KEY_ID=your-key-id
AWS_SECRET_ACCESS_KEY=your-secret-key
BUCKET=your-bucket-name

次にGemfileに

Gemfile
gem `aws-sdk', '~3'

を追加して

$ bundle install

そして環境変数に保存した値を使ってS3のインスタンスを作成します。

config/initializers/aws.rb
Aws.config.update({
    region: 'ap-northeast-1',
    credentials: Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY']),
})
  
S3_BUCKET = Aws::S3::Resource.new.bucket(ENV['S3_BUCKET'])

GET 要求に対して、 ブラウザから S3 へ POST するために必要な情報を返すアクションを実装します。
今回は video モデルのコントローラーにアクションを追加しました。

VideosController < ApplicationController

  def upload
    filename = params[:filename]
    filetype = params[:filetype]
        
    post = S3_BUCKET.presigned_post(
      key: "upload_video/#{filename}",
      acl: 'public-read',
      content_type: filetype,
      metadata: {
        'original-filename' => filename
      }
    })
    render json: {url: post.url,fields: post.fields}
  end
  
end

バケット内の保存先はkey:で指定するので、この値をDBに保存してモデルと紐づけることが可能です。

GET リクエストで filename,filetype パラメータ受け取ったuploadアクションは以下のpresigned POSTとして次のjsonを返します。

{
    "url": "https://your-bucket-name.s3.ap-northeast-1.amazonaws.com",
    "fields": {
        "key": "upload_video/test.mp4",
        "acl": "public-read",
        "Content-Type": "video/mp4",
        "x-amz-meta-original-filename": "test.mp4",
        "policy": "eyJleHBpc...",
        "x-amz-credential": "oiMjAxO...",
        "x-amz-algorithm": "AWS4-HMAC-SHA256",
        "x-amz-date": "20190607T004657Z",
        "x-amz-signature": "mF0aW9uIj..."
    }
}

ブラウザページの作成

動作としては

  • RailsにGETリクエストを送ってpresigned POSTを受け取る
  • presigned POSTを使ってS3へPOST
  • 実装はfetch api

です

<!DOCTYPE html>
<html>
  <head>
    <title>S3 POST Form</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  </head>
 
  <body>
    <input type="file" id="up_file">
    <br><input type="button" id="send" onclick="upload();" value="アップロード">
    <!-- fetch api -->    
    <script>
      function upload(){
        const up_files = document.getElementById('up_file');
        const up_file = up_files.files[0];
        if (up_files.value === "") {
          return false;
        }
        const url= 'http://localhost:3000/api/v1/video_upload/get_post_fields?filename=' + up_file.name + "&filetype=" + up_file.type;
        // Rails に GET
        console.log("GET 開始");
        fetch(
          url, 
          {method: 'GET'}
        ).then(response => {
          if(response.ok){
            console.log("GET 成功");
            return response.json();
          }
        }).then((data)=>{
          formdata = new FormData()
          for (key in data.fields) {
            formdata.append(key,data.fields[key]);
          }
          formdata.append("file",up_file);
          const headers = {
          "accept": "multipart/form-data"
          }
          // S3 に POST
          console.log("POST 開始");
          fetch(
            data.url,
            {
              method: 'POST',
              headers,
              body: formdata
            }
          ).then((response) => {
            if(response.ok){
              console.log("POST 成功");
              return response.text();
            }
          })
        });
      }
    </script>
  </body>
</html>

これでブラウザからのS3へのダイレクトアップロードを実装することができました。

参考

23
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?