Go to list of users who liked
More than 5 years have passed since last update.
Heroku Dev Center「Direct to S3 Image Uploads in Rails」の覚え書きです。
環境
- Rails 4.1.8
- heroku-toolbelt 3.16.0
- aws-sdk 1.59.0
- JQuery UI (1.11.2)
- jQuery File Upload Plugin (9.8.0)
サンプル
参照
- Direct to S3 Image Uploads in Rails | Heroku Dev Center
- CORS(Cross-Origin Resource Sharing)について整理してみた | Developers.IO
覚え書き
Philosophy
この記事ではjQuery-File-Uploadプラグインと AWS gem を用います。
画像をS3に直接アップロードできるCarrierWaveDirect のような他のライブラリもありますが、クライアント側に関する低レベルの知識なしでのそれらの実装は困難です。
jQuery-File-Upload プラグインを用いると、JavaScript のコードは比較的読みやすく短いものになり、そのコードを任意の画像アップロード入力フォームとして再利用することもできます。
また、ユーザインタフェースは非常にカスタマイズ可能です。
Rails側では、AWSで事前に署名したPOSTを生成し、画像のURLをデータベースに保存します。
Example app
このアプリではUser
モデルを持ち、アバター画像をユーザー単位で S3 に保存したいと思います。
rails new direct-s3-examplecddirect-s3-examplerails generate scaffold user name avatar_urlrake db:migrate
S3
ファイルを S3 に送る前に、S3 のアカウントと、適切に構成されたバケットが必要になります。
S3 SDK
次に Ruby で S3 とやりとりするためのライブラリが必要になります。
aws-sdk を Gemfile に追加してbundle install
してください。
gem'aws-sdk'
ローカルの開発では、.env
ファイルとForeman を使います。
次のように環境変数を追加してください。
尚、下記の値は例ですので、S3 で設定/取得したものを書いてください。
S3_BUCKET=my-s3-developmentAWS_ACCESS_KEY_ID=EXAMPLEKVFOOOWWPYAAWS_SECRET_ACCESS_KEY=exampleBARZHS3sRew8xw5hiGLfroD/b21p2l
確認
$foreman run rails runner"puts ENV['S3_BUCKET']"my-s3-development
環境変数を設定できたら、コントローラーから使えるように S3 オブジェクトを用意します。
AWS.config(access_key_id:ENV['AWS_ACCESS_KEY_ID'],secret_access_key:ENV['AWS_SECRET_ACCESS_KEY'])S3_BUCKET=AWS::S3.new.buckets[ENV['S3_BUCKET']]
Cross origin support
(参考)CORS(Cross-Origin Resource Sharing)について整理してみた
環境に合わせてバケットの CORS 設定を変更します。
適切なオリジンをAllowedOrigin
に設定しましょう。
<?xml version="1.0" encoding="UTF-8"?><CORSConfigurationxmlns="http://s3.amazonaws.com/doc/2006-03-01/"><CORSRule><AllowedOrigin>*</AllowedOrigin><AllowedMethod>GET</AllowedMethod><AllowedMethod>POST</AllowedMethod><AllowedMethod>PUT</AllowedMethod><AllowedHeader>*</AllowedHeader></CORSRule></CORSConfiguration>
Pre-signed post
AWS ruby gem で事前に署名した(Pre-signed)POSTを生成します。
あなたのユーザーや顧客が特定のオブジェクトをあなたのバケットにアップロードしたい場合に、事前に署名したURLは役立ちます。詳しくはClass: AWS::S3::PresignedPost をご覧ください。
users_controller.rb に pre-signed post を生成する行を追加します。
# GET /users/newdefnew@s3_direct_post=S3_BUCKET.presigned_post(key:"uploads/#{SecureRandom.uuid}/${filename}",success_action_status:201,acl: :public_read)@user=User.newend
Client side code
一時的なキャッシュとしてサーバーに頼ることはできないので、S3 にファイルを配信するために、クライアント側のコード(JavaScript)を使用しなければなりません。
HTML 5はファイルAPIを導入していますが、IE 10までサポートされませんでした。これを回避するために、jQuery File Upload を使います。これを使うには先に JQuery UI が必要になります。
- JQuery UI
- jQuery File Upload
Rails プロジェクトに JavaScript ファイルを取り込みます。
$curl\https://raw.githubusercontent.com/jquery/jquery-ui/master/ui/widget.js\>> app/assets/javascripts/jquery.ui.widget.js$curl\https://raw.githubusercontent.com/blueimp/jQuery-File-Upload/master/js/jquery.fileupload.js\>> app/assets/javascripts/z.jquery.fileupload.js
..//= require jquery.ui.widget.js//= require z.jquery.fileupload//= require_tree .
確認
rails sopen http://localhost:3000/users/new
>console.log($().fileupload)function(options){varisMethodCall=typeofoptions==="string",args=slice.call(arguments,1),returnValue=this;//...
Prepare the view
- form_for に directUpload クラスを追加
- avatar_url を f.file_field に変更
<%=form_for(@user,html:{class:"directUpload"})do|f|%><%if@user.errors.any?%>..<divclass="field"><%=f.label:avatar_url%><br><%=f.file_field:avatar_url%></div>
Detecting file field on the client side
以下のものが用意できました。
- S3 バケット
- 有効な pre-signed post オブジェクト
- User モデル(avatar_url、ファイル入力)
ユーザーの画像を S3 から取得したり、avatar_url に URL を保存する必要がありますが、これは主に JavaScript での手動処理になります。
・・・省略・・・
プログレスバーのスタイルを指定します。
.progress{max-width:600px;margin:0.2em00.2em0;}.progress.bar{height:1.2em;padding:0.2em;color:white;display:none;}
Finished jquery-file-upload code
・・・省略・・・
jQuery-File-Upload callbacks
タグ<script></script>
を追加します。
<h1>New user</h1><%=render'form'%><%=link_to'Back',users_path%><script>$(function(){$('.directUpload').find("input:file").each(function(i,elem){varfileInput=$(elem);varform=$(fileInput.parents('form:first'));varsubmitButton=form.find('input[type="submit"]');varprogressBar=$("<div class='bar'></div>");varbarContainer=$("<div class='progress'></div>").append(progressBar);fileInput.after(barContainer);fileInput.fileupload({fileInput:fileInput,url:'<%= @s3_direct_post.url %>',type:'POST',autoUpload:true,formData:<%=@s3_direct_post.fields.to_json.html_safe%>,paramName:'file',dataType:'XML',replaceFileInput:false,progressall:function(e,data){varprogress=parseInt(data.loaded/data.total*100,10);progressBar.css('width',progress+'%')},start:function(e){submitButton.prop('disabled',true);progressBar.css('background','green').css('display','block').css('width','0%').text("Loading...");},done:function(e,data){submitButton.prop('disabled',false);progressBar.text("Uploading done");// extract key and generate URL from responsevarkey=$(data.jqXHR.responseXML).find("Key").text();varurl='//<%= @s3_direct_post.url.host %>/'+key;// create hidden fieldvarinput=$("<input />",{type:'hidden',name:fileInput.attr('name'),value:url})form.append(input);},fail:function(e,data){submitButton.prop('disabled',false);progressBar.css("background","red").text("Failed");}});});});</script>
備考
# .envの環境変数を参照するためforemanで起動foreman run rails server
# 確認aws--profile foo s3lss3://aaa-bbb-ccc-1/uploads/# 全て削除(注意!)aws--profile bar s3rms3://aaa-bbb-ccc-1/uploads/--recursive
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme