Настройка Content-Type в прямой загрузке S3 с использованием Rails и загрузки файла jQuery

Я видел много вопросов здесь по этой теме, но ничего, с которыми я столкнулся, не работал у меня, поэтому здесь я отправляю еще...

Я на Ruby on Rails пытается настроить загрузку файлов прямо на Amazon S3 с помощью jQuery File Upload plugin. Я пошел вместе с очень полезным учебником Heroku, чтобы начать работу с исходными настройками. Файлы загружены в порядке, но все они были помечены как Content-Type: binary/octet-stream в S3, поэтому, когда они были представлены в приложении, все файлы загружались, а не открывались напрямую.

Это проблема, потому что я пытаюсь разрешить изображения, PDF файлы, аудио- или видеофайлы, поэтому мне нужно иметь возможность захватить правильный Content-Type из файла и передать его на S3. При просмотре документа AWS-SDK gem на Amazon я увидел этот раздел о добавлении .where(:content_type).starts_with("") в конец назначенного объекта post для изменения политика. Однако, когда я это сделал, он сделал ошибку:

<Error><Code>AccessDenied</Code>
<Message>Invalid according to Policy: Policy Condition failed: ["starts-with", "$Content-Type", ""]</Message>

Итак, я добавил в content_type: "" в hash выбора для назначенного объекта post, и теперь он работает снова, но вместо всех файлов по умолчанию binary/octet-stream все они по умолчанию равны image/jpeg. Здесь мой код на данный момент:

контроллер

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");
        }
      });
    });
  });

Как правильно отправить Content-Type на S3?

Ответ 1

Замените блок добавления:

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

Обратный вызов правильный, но data.formData уже взяла исходную версию fd. Просто установите его снова с измененным fd, и вам должно быть хорошо идти.

Обновите метод контроллера, чтобы вы не делали это дважды:

@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

Если кто-то ищет обновленный ответ на этот вопрос, я смог решить это:

  • Добавление типа содержимого начинается с 'к моему назначенному сообщению s3, как описано на этой странице: http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
  • Добавление скрытого ввода с именем "Content-Type" в мою форму, а затем его обновление каждый раз, когда выбран файл.

Ответ 3

Я считаю, что самый последний синтаксис для aws-sdk (2.9.6):

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