I've seen many questions here on this topic but nothing I've come across has worked for me, so here I am posting another...
I'm on Ruby on Rails trying to configure file uploads direct to Amazon S3 using the jQuery File Upload plugin. I followed along with the very helpful Heroku tutorial to get the initial setup working. Files uploaded fine, but they were all labeled as Content-Type: binary/octet-stream
in S3, so when they were served in the app, all files would download instead of opening directly.
This is a problem because I'm trying to allow images, PDFs, audio or video files, so I need to be able to grab the correct Content-Type
from the file and pass it on to S3. In looking at the AWS-SDK gem docs on Amazon, I saw this section about adding .where(:content_type).starts_with("")
to the end of the presigned post object to modify the policy. However, when I did that, it threw an error:
<Error><Code>AccessDenied</Code>
<Message>Invalid according to Policy: Policy Condition failed: ["starts-with", "$Content-Type", ""]</Message>
So I added in content_type: ""
into the opts hash for the presigned post object, and now it works again, but instead of all files defaulting to binary/octet-stream
they all default to image/jpeg
. Here's my code as of now:
Controller
def new
@s3_direct_post = S3_BUCKET.presigned_post(
key: "uploads/#{SecureRandom.uuid}/${filename}",
success_action_status: 201,
acl: :public_read,
content_type: "").where(:content_type).starts_with("")
end
_form.html.haml
:javascript
$(function() {
$('.directUpload').find("input:file").each(function(i, elem) {
var fileInput = $(elem);
var form = $(fileInput.parents('form:first'));
var submitButton = form.find('input[type="submit"]');
var progressBar = $("<div class='bar'></div>");
var barContainer = $("<div class='progress'></div>").append(progressBar);
var fd = #{@s3_direct_post.fields.to_json.html_safe};
fileInput.after(barContainer);
fileInput.fileupload({
// This 'add' section is where I thought to set the Content-Type, but I've tried with and without it and Content-Type remains the same on S3
add: function (e, data) {
fd["Content-Type"] = data.files[0].type;
console.log(fd); // The JSON object shows Content-Type correctly in console
data.submit();
},
fileInput: fileInput,
url: '#{@s3_direct_post.url}',
type: 'POST',
autoUpload: true,
formData: fd, // My updated JSON object
paramName: 'file',
dataType: 'XML',
replaceFileInput: false,
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
progressBar.css('width', progress + '%')
},
start: function (e) {
submitButton.prop('disabled', true);
progressBar.
css('background', 'green').
css('display', 'block').
css('width', '0%').
text("Loading...");
},
done: function(e, data) {
submitButton.prop('disabled', false);
progressBar.text("Uploading done");
// extract key and generate URL from response
var key = $(data.jqXHR.responseXML).find("Key").text();
var url = 'https://d295xbrl26r3ll.cloudfront.net/' + key.replace(/ /g, "%20");
// create hidden field
var input = $("<input />", { type:'hidden', name: 'item[file_url]', value: url })
form.append(input);
},
fail: function(e, data) {
submitButton.prop('disabled', false);
progressBar.
css("background", "red").
text("Failed");
}
});
});
});
How do I send the Content-Type
properly to S3?
If anyone is looking for an updated answer to this question, I was able to solve this by:
I believe the most current syntax for aws-sdk (2.9.6) is:
Replace your add block with:
The callback is correct, but data.formData already took the original version of fd. Just set it again with your modified fd and you should be good to go.
Update the controller method too so you're not doing it twice: