Javascript 使用jquery.fileupload直接上传到amazon S3

Javascript 使用jquery.fileupload直接上传到amazon S3,javascript,ruby-on-rails,amazon-web-services,amazon-s3,jquery-file-upload,Javascript,Ruby On Rails,Amazon Web Services,Amazon S3,Jquery File Upload,在我的应用程序中,我希望能够将文件直接从浏览器上传到我的AWS S3存储桶。我的后端是rails,但我希望避免额外跳到我的服务器,并避免使用诸如曲别针、carrierwave、carrierwave_direct等宝石,以保持简单。我从heroku那里大致了解到了这一点 我正在使用aws sdk gem和jquery.fileupload.js库 问题是,当我尝试上传时,我从AWS收到了400个错误请求 我不认为这是CORS的问题。我在我的bucket上配置了CORS,我可以看到一个成功的选项请

在我的应用程序中,我希望能够将文件直接从浏览器上传到我的AWS S3存储桶。我的后端是rails,但我希望避免额外跳到我的服务器,并避免使用诸如曲别针、carrierwave、carrierwave_direct等宝石,以保持简单。我从heroku那里大致了解到了这一点

我正在使用aws sdk gem和jquery.fileupload.js库

问题是,当我尝试上传时,我从AWS收到了400个错误请求

我不认为这是CORS的问题。我在我的bucket上配置了CORS,我可以看到一个成功的选项请求,然后是文件上传的POST请求,它返回400个错误请求

下面是一个简化的演示,它复制了这个问题

这是控制器操作。它生成一个AWS::S3::PresignedPost对象,以便视图可以使用它将文件直接发布到S3

  def new
    Aws.config.update({
      region: 'us-east-1',
      credentials: Aws::Credentials.new('[FILTERED]', '[FILTERED]'),
    })
    s3 = Aws::S3::Resource.new
    bucket = s3.bucket('mybucket')
    @presigned_post = bucket.presigned_post(key: "attachments/#{SecureRandom.uuid}/${filename}")
    @thing = Thing.new
  end
这是一个视图,
new.html.erb
,由上面的代码呈现,带有上传表单和javascript来处理上传

<div class='container'>
  <%= form_for(@thing, html: { class: 'direct_upload' }) do |f| %>
    <%= f.label 'Thing' %>
    <%= f.file_field :attachment_url %>
    <%= f.submit %>
  <% end %>
</div>

<script type="text/javascript">
  $(function() {
    var $form = $('form.direct_upload'),
      upload_url = '<%= escape_javascript(@presigned_post.url.to_s) %>',
      upload_form_data = '<%= escape_javascript(@presigned_post.fields.to_json.html_safe) %>';

    console.log('URL: ', upload_url);
    console.log('Form data: ', upload_form_data);

    if ($form.length) {
      $form.find('input[type=file]').each(function(index, input) {
        var $file_field = $(input);

        $file_field.fileupload({
          fileInput: $file_field,
          url: upload_url,
          type: 'POST',
          autoUpload: false,
          formData: upload_form_data,
          paramName: 'file',
          dataType: 'XML',

          add: function(e, data) {
            console.log('add callback fired.');
            $form.submit(function(e) {
              e.preventDefault();
              console.log('form submitted.');
              data.submit();
            });
          },
          start: function(e) {
            console.log('start callback fired');
          },
          done: function(e, data) {
            console.log('done callback fired');
          },
          fail: function(e, data) {
            console.log('fail callback fired');
            console.log(e);
            console.log(data);
          }
        });
      });
    }
  });
</script>
加载页面时,您可以在javascript控制台中看到预期的输出:

URL:  https://mybucket.s3.amazonaws.com/
Form data:  {"key":"attachments/d6313635-9735-4b84-9985-f9f62a036de8/${filename}","policy":"[FILTERED]","x-amz-credential":"[FILTERED]/us-east-1/s3/aws4_request","x-amz-algorithm":"AWS4-HMAC-SHA256","x-amz-date":"20150809T134239Z","x-amz-signature":"[FILTERED]"}
如您所见,有一个关键字段

当您将文件添加到文件输入字段时,
add
回调将激发并绑定表单的提交操作,如预期的那样。当表单提交时,请求转到S3,但是由于返回400,因此会触发
fail
回调

这可能描述了问题所在,但根据提供的信息,我无法解决问题

以下是从Chrome开发工具复制的请求/响应信息

Remote Address:54.231.17.17:443
Request URL:https://mybucket.s3.amazonaws.com/
Request Method:POST
Status Code:400 Bad Request

Response Headers
Access-Control-Allow-Methods:GET, POST, PUT
Access-Control-Allow-Origin:*
Connection:close
Content-Type:application/xml
Date:Sun, 09 Aug 2015 12:29:57 GMT
Server:AmazonS3
Transfer-Encoding:chunked
Vary:Origin, Access-Control-Request-Headers, Access-Control-Request-Method
x-amz-id-2:ymrt0MUlhf3bKqVWj+O5jhaUPXNEXy9HQh9PABmqzDkkb4Ods3Hy1LA++8G/Svri3LcOktpnGeE=
x-amz-request-id:545E755033D285F2

Request Headers
Accept:application/xml, text/xml, */*; q=0.01
Accept-Encoding:gzip, deflate
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:331
Content-Type:multipart/form-data; boundary=----WebKitFormBoundary9vtTme67oAg1OMyL
Host:braidio.s3.amazonaws.com
Origin:http://localhost:3000
Referer:http://localhost:3000/things/new
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36

Request Payload
------WebKitFormBoundary9vtTme67oAg1OMyL
Content-Disposition: form-data; name="file"; filename="my_text.txt"
Content-Type: text/plain


------WebKitFormBoundary9vtTme67oAg1OMyL--
如您所见,请求负载只包含文件,而不包含密钥。可能是文件需要在post请求中的所有其他字段之后,这就是为什么S3没有看到键字段,如本文所示

一些相关的宝石:

* jquery-rails (4.0.4)
* rails (4.2.3)
* aws-sdk (2.1.13)
* aws-sdk-core (2.1.13)
* aws-sdk-resources (2.1.13)
同时使用
jquery.fileupload.js 5.42.3

我不知道该怎么做


提前谢谢

我找到了解决办法

表单数据是在我在前端捕获的后端生成的:

upload_form_data = '<%= escape_javascript(@presigned_post.fields.to_json.html_safe) %>'
显然,
$.fileupload
函数需要formData的对象,而不是字符串

有了这个变更,所需的表单数据将被包含在到S3的POST中,并且它是成功的

以下是工作的javascript代码:

$(function() {
var $form = $('form.direct_upload'),
  upload_url,
  upload_form_data,
  upload_form_data_obj;

if ($form.length) {
  upload_url = '<%= escape_javascript(@presigned_post.url) %>'
  upload_form_data = '<%= escape_javascript(@presigned_post.fields.to_json.html_safe) %>';
  upload_form_data_obj = JSON.parse(upload_form_data);

  console.log('URL: ', upload_url);
  console.log('Form data: ', upload_form_data_obj);

  $form.find('input[type=file]').each(function(index, input) {
    var $file_field = $(input);
    $file_field.fileupload({
      fileInput: $file_field,
      url: upload_url,
      type: 'POST',
      autoUpload: false,
      formData: upload_form_data_obj, // needed to be an object, not a string
      paramName: 'file',
      dataType: 'JSON',

      add: function(e, data) {
        console.log('add callback fired.');
        $form.submit(function(e) {
          e.preventDefault();

          console.log('form submitted.');
          console.log(data);
          data.submit();
        });
      },
      start: function(e) {
        console.log('start callback fired');
      },
      done: function(e, data) {
        console.log('done callback fired');
      },
      fail: function(e, data) {
        console.log('fail callback fired');
        console.log(e);
        console.log(data);
      }
    });
  });
}
});
$(函数(){
var$form=$('form.direct_upload'),
上传你的网址,
上传表格数据,
上传表格数据对象;
如果($form.length){
上传地址=“”
上传表格数据=“”;
upload\u form\u data\u obj=JSON.parse(upload\u form\u data);
log('URL:',upload_URL);
console.log('Form data:',upload_Form_data_obj);
$form.find('input[type=file]')。每个(函数(索引,输入){
变量$file_字段=$(输入);
$file_field.fileupload({
fileInput:$file\u字段,
url:上传url,
键入:“POST”,
自动上载:false,
formData:upload\u form\u data\u obj,//必须是对象,而不是字符串
paramName:'文件',
数据类型:“JSON”,
添加:功能(e、数据){
log('add callback fired');
$form.submit(功能(e){
e、 预防默认值();
console.log(“已提交表单”);
控制台日志(数据);
data.submit();
});
},
开始:功能(e){
log('start callback fired');
},
完成:功能(e,数据){
log('done callback fired');
},
失败:功能(e,数据){
log('fail callback fired');
控制台日志(e);
控制台日志(数据);
}
});
});
}
});
upload_form_data_obj = JSON.parse(upload_form_data);
$(function() {
var $form = $('form.direct_upload'),
  upload_url,
  upload_form_data,
  upload_form_data_obj;

if ($form.length) {
  upload_url = '<%= escape_javascript(@presigned_post.url) %>'
  upload_form_data = '<%= escape_javascript(@presigned_post.fields.to_json.html_safe) %>';
  upload_form_data_obj = JSON.parse(upload_form_data);

  console.log('URL: ', upload_url);
  console.log('Form data: ', upload_form_data_obj);

  $form.find('input[type=file]').each(function(index, input) {
    var $file_field = $(input);
    $file_field.fileupload({
      fileInput: $file_field,
      url: upload_url,
      type: 'POST',
      autoUpload: false,
      formData: upload_form_data_obj, // needed to be an object, not a string
      paramName: 'file',
      dataType: 'JSON',

      add: function(e, data) {
        console.log('add callback fired.');
        $form.submit(function(e) {
          e.preventDefault();

          console.log('form submitted.');
          console.log(data);
          data.submit();
        });
      },
      start: function(e) {
        console.log('start callback fired');
      },
      done: function(e, data) {
        console.log('done callback fired');
      },
      fail: function(e, data) {
        console.log('fail callback fired');
        console.log(e);
        console.log(data);
      }
    });
  });
}
});