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

More than 1 year has passed since last update.

こちらを参考に, 画像をブラウザから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

以上です.