jQuery 使用 CORS headers 将文件上传到 S3(和 rails)

jQuery file upload to S3 (and rails) with CORS headers

我正在尝试将文件直接上传到 S3,并在上传时显示进度条。 但是当我提交表单时,我收到以下错误消息:

OPTIONS https://s3-eu-west-1.amazonaws.com/my-bucket 403 (Forbidden) 9:1 XMLHttpRequest cannot load

https://s3-eu-west-1.amazonaws.com/my-bucket. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://localhost:3000' is therefore not allowed access. The response had HTTP status code 403. 9:223 fail

这是我的表格:

%form#file_upload(action=@aws_s3_url 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 => "ACCESS_KEY"}
  %input{:type => :hidden, :name => :acl,  :value => :private}
  %input{:type => :hidden, :name => :success_action_redirect}
  %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}

这是我的 javascript 代码,使用 jquery-file-upload 插件:

$(function() {
  $('#file_upload').fileupload({
    //forceIframeTransport: true,    // VERY IMPORTANT.  you will get 405 Method Not Allowed if you don't add this.
    autoUpload: true,
    type: 'POST',
    dataType: 'xml',
    url: $(this).attr('action'),
    add: function (event, data) {
      $.ajax({
        url: "/projects/9/create_file",
        type: 'GET',
        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);
          $('#file_upload').find('input[name=success_action_redirect]').val(retdata.success_action_redirect);
        }

      });

      console.log(data)
      file = data.files[0]
      data.context = $(tmpl("template-upload", file))
      $('#file_upload').append(data.context)

      data.submit();
    },
    progress: function (event, data) {
      progress = parseInt(data.loaded / data.total * 100, 10)
      console.log("Progress")
      data.context.find('.bar').css('width', progress + '%')
    },
    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();
      console.log("Loading")
    },
    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();
    },
  });
});

我知道代码有效,因为当我取消对 forceIframeTransport 的注释时,它会处理请求。问题是使用该方法您只能获得一次进度事件(完成时),因此它违背了拥有进度条的目的。我读到没有 forceIframeTransport,你必须设置 CORS headers,我在 rails 中这样做:

before_filter :cors_preflight_check
after_filter :cors_set_access_control_headers

#, DELETE, OPTIONS
#DELETE, OPTIONS

def cors_set_access_control_headers
  headers['Access-Control-Allow-Origin'] = 'https://0.0.0.0:3000/*'
  headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT'
  headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization, Token'
  headers['Access-Control-Max-Age'] = '3000'
end

def cors_preflight_check
  if request.method == 'OPTIONS'
    headers['Access-Control-Allow-Origin'] = 'https://0.0.0.0:3000/*'
    headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT'
    headers['Access-Control-Allow-Headers'] = 'X-Requested-With, X-Prototype-Version, Token'
    headers['Access-Control-Max-Age'] = '3000'

    render :text => '', :content_type => 'text/plain'
  end
end

它们确实被发送了,因为当我手动执行 GET“/projects/9/create_file”(例如使用 advance rest 插件)时,我得到以下响应 headers:

Access-Control-Allow-Origin: https://0.0.0.0:3000/*

Access-Control-Allow-Methods: POST, GET, PUT

Access-Control-Allow-Headers: Origin, Content-Type, Accept,

Authorization, Token Access-Control-Max-Age: 3000

此外,这是我在亚马逊的 CORS 配置:

<CORSRule>
    <AllowedOrigin>https://0.0.0.0:3000/*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>

有什么可能出错的想法吗?

要允许 https://example.com 上的任何子路径,您可以这样做:

<CORSRule>
    <AllowedOrigin>https://example.com</AllowedOrigin>
</CORSRule>

origin header field 可以是通配符 (*) 或包含一个或不包含通配符的 url。例如:

https://*.example.com

然而,在末尾添加通配符 https://0.0.0.0:3000/* 将不允许任何可能猜到的路径。