こちらを参考に, 画像をブラウザからS3へ直接アップロードできるようにしました.
1. AWSの準備 (時間:5分)
IAMでS3アクセス用のユーザを作成します. まずは以下のようにコンソール画面からIAMへアクセス.
任意の名前を入力. プログラマティックアクセスにチェックして次へ.
既存ポリシーの中から AmazonS3FullAccessを見つけてチェック.
以上でユーザ作成は完了. 詳細画面でCreate Access keyをクリックして必要なキーを取得する.
このようにkeyIDとシークレットkeyが表示されるので, メモっておく.
次にS3の設定を行う.
適当なバケットを作成する. 各種設定はデフォルトのままで良い. 作成し終えたら, そのバケットのプロパティを開き, Permissions -> CORS configurationに以下のように記述する. これで終わり.
<?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へ直接アップロードされます.
以上です.