Setting the Content-Type in direct to S3 upload using Rails and jQuery File Upload

喜夏-厌秋 提交于 2019-11-27 18:10:32

问题


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?


回答1:


Replace your add block with:

      fd["Content-Type"] = data.files[0].type;  
      data.formData = fd;
      data.submit();

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:

@s3_direct_post = S3_BUCKET.presigned_post(
                  key: "uploads/#{SecureRandom.uuid}/${filename}",
                  success_action_status: 201,
                  acl: :public_read).where(:content_type).starts_with("")



回答2:


If anyone is looking for an updated answer to this question, I was able to solve this by:

  1. Adding a 'content type starts with' clause to my s3 presigned post, as documented on this page: http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
  2. Adding a hidden input named 'Content-Type' to my form and then updating it accordingly whenever a file is selected.



回答3:


I believe the most current syntax for aws-sdk (2.9.6) is:

@s3_direct_post = S3_BUCKET.presigned_post(
                  key: "uploads/#{SecureRandom.uuid}/${filename}",
                  success_action_status: 201,
                  acl: 'public-read',
                  content_type_starts_with: '')


来源:https://stackoverflow.com/questions/25315658/setting-the-content-type-in-direct-to-s3-upload-using-rails-and-jquery-file-uplo

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!