SWFUpload direct to Amazon S3 in Ruby on Rails

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!

 

Add your comment:

Please login through: