【Rails】ブラウザからS3へ画像を直接アップロード

  • 2
    いいね
  • 0
    コメント

こちらを参考に, 画像をブラウザからS3へ直接アップロードできるようにしました.

1. AWSの準備 (時間:5分)

IAMでS3アクセス用のユーザを作成します. まずは以下のようにコンソール画面からIAMへアクセス.

ss 2017-09-18 12.44.57.png

次に, 以下のUsersをクリック
ss 2017-09-18 12.48.08.png

Add userをクリック
ss 2017-09-18 12.48.16.png

任意の名前を入力. プログラマティックアクセスにチェックして次へ.
ss 2017-09-18 12.48.37.png

既存ポリシーの中から AmazonS3FullAccessを見つけてチェック.
eee

以上でユーザ作成は完了. 詳細画面でCreate Access keyをクリックして必要なキーを取得する.
aaa

このようにkeyIDとシークレットkeyが表示されるので, メモっておく.
ss 2017-09-18 12.50.27.png

次にS3の設定を行う.

適当なバケットを作成する. 各種設定はデフォルトのままで良い. 作成し終えたら, そのバケットのプロパティを開き, Permissions -> CORS configurationに以下のように記述する. これで終わり.

ssd

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

2. バックエンドでS3ポリシーを作成

自身のRailsアプリに, POSTメソッドで叩ける適当なエンドポイントを作成する.

routes.rbに例えば以下のように記述.

post 'image_upload', to: 'images#upload'

そして, images#uploadを次のように定義する.

class ImagesController < ApplicationController
  # Deviceを使って認証処理をしている場合, 次の1行によって認証を要求できる.
  before_action :authenticate_user!, only: [:upload]

  # 先ほど作ったバケットの名前とアクセスKeyIdとシークレットkey
  S3_BUCKET = 'your-bucket-name'
  AWS_ACCESS_KEY_ID = 'XXX'
  AWS_SECRET_KEY = 'XXX'

  def upload
    # アップロード後のファイル名
    key = 'hogehoge'

    acl = 'public-read'
    ctype = params[:content_type]

    # ポリシー作成
    policy_document = {
      # 1分間のみ有効
      expiration: (Time.now + 1.minute).utc,
      conditions: [
        # アップロード先のS3バケット
        { bucket: S3_BUCKET },
        # ファイルの権限
        { acl: acl },
        # ファイル名
        { key: key },
        # ファイルの形式
        { 'Content-Type' => ctype },
        # アップロード可能なファイルのサイズ
        ['content-length-range', params[:size], params[:size]]
      ]
    }.to_json
    policy = Base64.encode64(policy_document).gsub("\n", '')

    # signatureの作成
    signature = Base64.encode64(
        OpenSSL::HMAC.digest(
            OpenSSL::Digest::Digest.new('sha1'),
            AWS_SECRET_KEY, policy)).gsub('\n', '')

    # アップロードに必要な情報をJSON形式でクライアントに返す
    render json: {
      url: "https://#{S3_BUCKET}.s3.amazonaws.com/",
      form: {
        AWSAccessKeyId: AWS_ACCESS_KEY_ID,
        signature: signature,
        policy: policy,
        key: key,
        acl: acl,
        'Content-Type' => ctype
      }
    }
  end
end

3. フロントエンドでポリシー取得しアップロード実行

画像をアップロードしたいViewに, 以下のようなjavascriptを仕込みます.


画像を投稿します

<div style="width: 500px">
  <form enctype="multipart/form-data" method="post">
    <input type="file" name="userfile" accept="image/*">
  </form>
</div>

<div id="thumbnail" style="max-width: 100px;">
  <img src="/plus.png" id="image_to_upload">
</div>

<button class="btn btn-primary" id="upload">投稿</button>

<script type="text/javascript">
$(function() {
  var file = null;

  // アップロードするファイルを選択
  $('input[type=file]').change(function() {
    file = $(this).prop('files')[0];
    // 画像以外は処理を停止させるためファイル形式をチェック
    if (file.type != 'image/jpeg' && file.type != 'image/png') {
      // 画像でない場合は消す
      var img_src = $('<img>').attr('src', '/plus.png')
      $('#thumbnail').html(img_src);
      file = null
      return;
    }

    // サムネ表示
    var reader = new FileReader();
    reader.onload = function() {
      var img_src = $('<img>').attr('src', reader.result)
      img_src.css('width', '500px');
      $('#thumbnail').html(img_src);
    }
    reader.readAsDataURL(file);
  });

  // アップロードボタンクリック
  $('#upload').click(function(){
    // ファイルが指定されていなければ何も起こらない
    if(!file) {
      return;
    }

    // ポリシーを発行する
    $.ajax({
      url: 'http://localhost:3000/image_upload',
      type: 'POST',
      data: {
        content_type: file.type,
        size: file.size
      }
    })
    .done(function( data, textStatus, jqXHR ) {
      // 取得したポリシーをフォームデータの形に整形する
      var name, fd = new FormData();
      for (name in data.form) if (data.form.hasOwnProperty(name)) {
        fd.append(name, data.form[name]);
      }
      fd.append('file', file); // ファイルを添付

      $.ajax({
        url: data.url,
        type: 'POST',
        dataType: 'json',
        data: fd,
        processData: false,
        contentType: false
      })
      .done(function( data, textStatus, jqXHR ) {
        console.log('success!')
      })
      .fail(function( jqXHR, textStatus, errorThrown ) {
        // アップロード時のエラー
        console.log('error: 2'); 
      });  

    })
    .fail(function( jqXHR, textStatus, errorThrown ) {
      // ポリシー取得時のエラー
      console.log('error: 1');
    });

  });

});
</script>

次のようなUIが出来上がります. 「ファイルを選択」をクリックしてファイルを適当に選択すると, サムネイルが表示されます. この状態で投稿ボタンを押すと画像がS3へ直接アップロードされます.
ss 2017-09-18 13.16.07.png

以上です.