Im trying to upload a file directly to S3, and displaying a progress bar while it uploads. But when I submit the form, I get the following error message:
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
This is my form:
%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}
And this is my javascript code, using the jquery-file-upload
plugin:
$(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();
},
});
});
I know the code works because when I uncoment forceIframeTransport it processes the request. The problem is that using that method you only get a progress event once (when is completed), so it defeats the purpose of having a progress bar. I read that to do without the forceIframeTransport, you have to set the CORS headers, which I do in rails like this:
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
And they are indeed sent, because when I manually do the GET "/projects/9/create_file" (With the advance rest plugin for example) i get the following response 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
Also, this is my CORS configuration in amazon:
<CORSRule>
<AllowedOrigin>https://0.0.0.0:3000/*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
Any ideas about what could be wrong?
To allow any subpath on
https://example.com
you would do:The origin header field can either be a wildcard (*) or a url containing one or no wildcard. Ex:
However adding the wildcard on the end
https://0.0.0.0:3000/*
will not allow any path as one could guess.