直接上传到 Amazon S3


本文介绍了如何使用插件最新版本搭配 rails 将文件直接上传至 S3。

它存在好几个先决条件,但最终它会是一个靠谱的解决方案!

在开始之前,你需要确保已经引用了插件资源包中的以下 js 脚本:

  • jquery.ui.widget
  • jquery.iframe-transport
  • jquery.fileupload

好了,然后你需要有一个类似于这样的表单:

%form#file_upload(action="https://YOURBUCKET.s3.amazonaws.com" method="post" enctype="multipart/form-data")
  -# order is important!
  -# also, the things that are not filled in right now *will* be filled in soon.  See below.
  %input{:type => :hidden, :name => :key}
  %input{:type => :hidden, :name => "AWSAccessKeyId", :value => "YOUR_ACCESS_KEY"}
  %input{:type => :hidden, :name => :acl,  :value => :private}
  %input{:type => :hidden, :name => :success_action_status, :value => "200"}
  %input{:type => :hidden, :name => :policy}
  %input{:type => :hidden, :name => :signature}

  .fileupload-content
    .fileupload-progress
  .file-upload
    %label.fileinput-button
      %span Upload Document
      %input{:type => :file, :name => :file}

... 要有对应的脚本来响应该表单,代码如下:

$(function() {
  $('#file_upload').fileupload({
    forceIframeTransport: true,    // VERY IMPORTANT.  you will get 405 Method Not Allowed if you don't add this.
    autoUpload: true,
    add: function (event, data) {
      $.ajax({
        url: "/documents",
        type: 'POST',
        dataType: 'json',
        data: {doc: {title: data.files[0].name}},
        async: false,
        success: function(retdata) {
          // after we created our document in rails, it is going to send back JSON of they key,
          // policy, and signature.  We will put these into our form before it gets submitted to amazon.
          $('#file_upload').find('input[name=key]').val(retdata.key);
          $('#file_upload').find('input[name=policy]').val(retdata.policy);
          $('#file_upload').find('input[name=signature]').val(retdata.signature);
        }
        
      });

      data.submit();
    },
    send: function(e, data) {
      // show a loading spinner because now the form will be submitted to amazon, 
      // and the file will be directly uploaded there, via an iframe in the background. 
      $('#loading').show();
    },
    fail: function(e, data) {
      console.log('fail');
      console.log(data);
    },
    done: function (event, data) {
      // here you can perform an ajax call to get your documents to display on the screen.
      $('#your_documents').load("/documents?for_item=1234");
      
      // hide the loading spinner that we turned on earlier.
      $('#loading').hide();
    },
  });
});

最后,你的 controller 可以参考以下示例:

# create the document in rails, then send json back to our javascript to populate the form that will be
  # going to amazon.
  def create
    @document = Document.create(params[:doc])
    render :json => {
      :policy => s3_upload_policy_document, 
      :signature => s3_upload_signature, 
      :key => @document.s3_key, 
      :success_action_redirect => document_upload_success_document_url(@document)
    }
  end
  
  # just in case you need to do anything after the document gets uploaded to amazon.
  # but since we are sending our docs via a hidden iframe, we don't need to show the user a 
  # thank-you page.
  def s3_confirm
    head :ok
  end
 
  private
  
  # generate the policy document that amazon is expecting.
  def s3_upload_policy_document
    return @policy if @policy
    ret = {"expiration" => 5.minutes.from_now.utc.xmlschema,
      "conditions" =>  [ 
        {"bucket" =>  YOUR_BUCKET_NAME}, 
        ["starts-with", "$key", @document.s3_key],
        {"acl" => "private"},
        {"success_action_status" => "200"},
        ["content-length-range", 0, 1048576]
      ]
    }
    @policy = Base64.encode64(ret.to_json).gsub(/\n/,'')
  end

  # sign our request by Base64 encoding the policy document.
  def s3_upload_signature
    signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), YOUR_SECRET_KEY, s3_upload_policy_document)).gsub("\n","")
  end