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!