I'm working on various projects and for one certain project this company wanted a file sharing website, like yousendit.com for example, but the site should be in-house. I proposed Amazon S3 for the storage of the files, otherwise the VPS will become very expensive. This file sharing website should also handle large files, so a reliable upload method is desired. SWFUpload is a well known Flash upload application. So the requirements are now complete: Ruby on Rails, Amazon S3 and SWFUpload.
First I created a config file to enter my Amazon S3 credentials. The credentials are dependent on the Ruby on Rails environment.
config/amazon_s3.yml
development: bucket_name: BUCKET_NAME access_key_id: ACCESS_KEY_ID secret_access_key: SECRET_ACCESS_KEY test: bucket_name: BUCKET_NAME access_key_id: ACCESS_KEY_ID secret_access_key: SECRET_ACCESS_KEY production: bucket_name: BUCKET_NAME access_key_id: ACCESS_KEY_ID secret_access_key: SECRET_ACCESS_KEY
Created a controller and added the index method. The method reads the S3 settings from the config file and generates the fields required for SWFUpload and S3.
def index filename = "#{RAILS_ROOT}/config/amazon_s3.yml" config = YAML.load_file(filename) bucket = config[ENV['RAILS_ENV']]['bucket_name'] access_key_id = config[ENV['RAILS_ENV']]['access_key_id'] secret_access_key = config[ENV['RAILS_ENV']]['secret_access_key'] key = ENV['RAILS_ENV'] acl = 'public-read' expiration_date = 10.hours.from_now.utc.strftime('%Y-%m-%dT%H:%M:%S.000Z') max_filesize = 2.gigabyte policy = Base64.encode64( "{'expiration': '#{expiration_date}', 'conditions': [ {'bucket': '#{bucket}'}, ['starts-with', '$key', '#{key}'], {'acl': '#{acl}'}, {'success_action_status': '201'}, ['starts-with', '$Filename', ''], ['content-length-range', 0, #{max_filesize}] ] }").gsub(/\n|\r/, '') signature = Base64.encode64( OpenSSL::HMAC.digest( OpenSSL::Digest::Digest.new('sha1'), secret_access_key, policy)).gsub("\n","") @post = { "key" => "#{key}/${filename}", "AWSAccessKeyId" => "#{access_key_id}", "acl" => "#{acl}", "policy" => "#{policy}", "signature" => "#{signature}", "success_action_status" => "201" } @upload_url = "http://#{bucket}.s3.amazonaws.com/" end
And the index.html.erb view.
<% content_for :head do %> <link href="/stylesheets/swfupload.css" rel="stylesheet" type="text/css" /> <% end%> <script type="text/javascript" src="/javascripts/swfupload/swfupload.js"></script> <script type="text/javascript" src="/javascripts/swfupload/swfupload.queue.js"></script> <script type="text/javascript" src="/javascripts/swfupload/fileprogress.js"></script> <script type="text/javascript" src="/javascripts/swfupload/handlers.js"></script> <script type="text/javascript"> var swfu; window.onload = function() { var settings = { flash_url : "/assets/swfupload.swf", upload_url: "<%= @upload_url %>", http_success : [ 200, 201, 204 ], // FOR AWS file_size_limit : "2 GB", file_types : "*.*", file_types_description : "All Files", file_upload_limit : 100, file_queue_limit : 0, file_post_name : "file", // FOR AWS custom_settings : { progressTarget : "fsUploadProgress", cancelButtonId : "btnCancel" }, debug: <%= ENV['RAILS_ENV']=='development' ? 'true' : 'false' %>, // Button settings button_image_url : "/images/buttonUploadText.png", button_placeholder_id : "spanButtonPlaceHolder", button_width: 61, button_height: 22, // The event handler functions are defined in handlers.js file_queued_handler : fileQueued, file_queue_error_handler : fileQueueError, file_dialog_complete_handler : fileDialogComplete, upload_start_handler : uploadStart, upload_progress_handler : uploadProgress, upload_error_handler : uploadError, upload_success_handler : uploadSuccess, upload_complete_handler : uploadComplete, queue_complete_handler : queueComplete, // Queue plugin event post_params: <%= @post.to_json %> // FOR AWS }; swfu = new SWFUpload(settings); }; </script> <div id="content"> <form id="form" action="/upload/upload" method="post" enctype="multipart/form-data"> <div class="fieldset flash" id="fsUploadProgress"> <span class="legend">Upload Queue</span> </div> <div id="divStatus">0 Files Uploaded</div> <div> <span id="spanButtonPlaceHolder"></span> <input id="btnCancel" type="button" value="Cancel All Uploads" onclick="swfu.cancelQueue();" disabled="disabled" style="margin-left: 2px; font-size: 8pt; height: 29px;" /> </div> </form> </div>
Upload the file crossdomain.xml to the root of your bucket. This is for Flash to upload to a different domain.
<?xml version="1.0"?> <!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <allow-access-from domain="*" secure="false" /> </cross-domain-policy>
Finally took the files from the SWFUpload simpledemo and placed them in the following directories:
- swfupload.swf in public/assets/
- fileprogress.js, handles.js, swfupload.js and swfupload.queue.js in public/javascripts/swfupload/
- buttonUploadText.png in public/images/
Now SWFUpload should be working in your Ruby on Rails application.
Callback
For my application I needed a callback to let my application know there was a file successfully uploaded to the S3 bucket. To get this functionality I added a function to the controller and modified the handlers.js file.
def upload_done file = ShareFile.new file.name = params[:name] file.filestatus = params[:filestatus] file.filetype = params[:type] file.size = params[:size] file.s3_available = true file.save end
function uploadSuccess(file, serverData) { // HERE: Send a notification upload has succeeded new Ajax.Request('/share/upload_done?'+Object.toQueryString(file), { method:'get', asynchronous: false, onSuccess: function(){ var progress = new FileProgress(file, this.customSettings.progressTarget); progress.setStatus("Sending meta data."); } }); // HERE: end try { var progress = new FileProgress(file, this.customSettings.progressTarget); progress.setComplete(); progress.setStatus("Complete."); progress.toggleCancel(false); } catch (ex) { this.debug(ex); } }
When SWFUpload is done uploading it uses the javascript callback to update the status of the form and to send a notification to the Ruby on Rails application.
Good luck!