1

В моем приложении я хотел бы иметь возможность загружать файлы непосредственно из браузера в мой ковш AWS S3. Мой бэкэнд - рельсы, но я бы хотел избежать лишнего хопа на моем сервере и избегать использования драгоценных камней, таких как paperclip, carrierwave, carrierwave_direct и т. Д., Чтобы это было просто. Я примерно следую за этим tutorial от heroku.Прямая загрузка в amazon S3 с помощью jquery.fileupload

Я использую драгоценный камень aws-sdk и jquery.fileupload.js lib.

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

Я не думаю, что это проблема CORS. У меня есть CORS, настроенный на моем ковше, и я вижу успешный запрос OPTIONS, за которым следует запрос POST для загрузки файла, который возвращает 400 плохих запросов.

Ниже приведена упрощенная демоверсия, которая реплицирует проблему.

Это действие контроллера. Он генерирует объект AWS :: S3 :: PresignedPost, чтобы представление могло использовать его для отправки файлов непосредственно на S3.

def new 
    Aws.config.update({ 
     region: 'us-east-1', 
     credentials: Aws::Credentials.new('[FILTERED]', '[FILTERED]'), 
    }) 
    s3 = Aws::S3::Resource.new 
    bucket = s3.bucket('mybucket') 
    @presigned_post = bucket.presigned_post(key: "attachments/#{SecureRandom.uuid}/${filename}") 
    @thing = Thing.new 
    end 

Это мнение, new.html.erb, который оказывает выше, с формой загрузки и JavaScript для обработки загрузки.

<div class='container'> 
    <%= form_for(@thing, html: { class: 'direct_upload' }) do |f| %> 
    <%= f.label 'Thing' %> 
    <%= f.file_field :attachment_url %> 
    <%= f.submit %> 
    <% end %> 
</div> 

<script type="text/javascript"> 
    $(function() { 
    var $form = $('form.direct_upload'), 
     upload_url = '<%= escape_javascript(@presigned_post.url.to_s) %>', 
     upload_form_data = '<%= escape_javascript(@presigned_post.fields.to_json.html_safe) %>'; 

    console.log('URL: ', upload_url); 
    console.log('Form data: ', upload_form_data); 

    if ($form.length) { 
     $form.find('input[type=file]').each(function(index, input) { 
     var $file_field = $(input); 

     $file_field.fileupload({ 
      fileInput: $file_field, 
      url: upload_url, 
      type: 'POST', 
      autoUpload: false, 
      formData: upload_form_data, 
      paramName: 'file', 
      dataType: 'XML', 

      add: function(e, data) { 
      console.log('add callback fired.'); 
      $form.submit(function(e) { 
       e.preventDefault(); 
       console.log('form submitted.'); 
       data.submit(); 
      }); 
      }, 
      start: function(e) { 
      console.log('start callback fired'); 
      }, 
      done: function(e, data) { 
      console.log('done callback fired'); 
      }, 
      fail: function(e, data) { 
      console.log('fail callback fired'); 
      console.log(e); 
      console.log(data); 
      } 
     }); 
     }); 
    } 
    }); 
</script> 

Это ответ возвращается из S3:

<Error> 
    <Code>InvalidArgument</Code> 
    <Message>Bucket POST must contain a field named 'key'. If it is specified, please check the order of the fields.</Message> 
    <ArgumentName>key</ArgumentName> 
    <ArgumentValue></ArgumentValue> 
    <RequestId>[filtered]</RequestId> 
    <HostId>[filtered]</HostId> 
</Error> 

При загрузке страницы вы можете увидеть ожидаемый результат в консоли JavaScript:

URL: https://mybucket.s3.amazonaws.com/ 
Form data: {"key":"attachments/d6313635-9735-4b84-9985-f9f62a036de8/${filename}","policy":"[FILTERED]","x-amz-credential":"[FILTERED]/us-east-1/s3/aws4_request","x-amz-algorithm":"AWS4-HMAC-SHA256","x-amz-date":"20150809T134239Z","x-amz-signature":"[FILTERED]"} 

Как вы можете видеть, есть ключевое поле.

Когда вы добавляете файл в поле ввода файла, обратный вызов add запускает и связывает действие отправки формы, как и ожидалось. Когда форма отправляется, запрос переходит на S3, но затем обратный вызов fail срабатывает, потому что возвращается 400.

Этот question может описывать, в чем проблема, но я не смог ее решить на основе предоставленной информации.

Ниже приведена информация о запросе/ответе, скопированная с помощью инструментов Chrome dev.

Remote Address:54.231.17.17:443 
Request URL:https://mybucket.s3.amazonaws.com/ 
Request Method:POST 
Status Code:400 Bad Request 

Response Headers 
Access-Control-Allow-Methods:GET, POST, PUT 
Access-Control-Allow-Origin:* 
Connection:close 
Content-Type:application/xml 
Date:Sun, 09 Aug 2015 12:29:57 GMT 
Server:AmazonS3 
Transfer-Encoding:chunked 
Vary:Origin, Access-Control-Request-Headers, Access-Control-Request-Method 
x-amz-id-2:ymrt0MUlhf3bKqVWj+O5jhaUPXNEXy9HQh9PABmqzDkkb4Ods3Hy1LA++8G/Svri3LcOktpnGeE= 
x-amz-request-id:545E755033D285F2 

Request Headers 
Accept:application/xml, text/xml, */*; q=0.01 
Accept-Encoding:gzip, deflate 
Accept-Language:en-US,en;q=0.8 
Connection:keep-alive 
Content-Length:331 
Content-Type:multipart/form-data; boundary=----WebKitFormBoundary9vtTme67oAg1OMyL 
Host:braidio.s3.amazonaws.com 
Origin:http://localhost:3000 
Referer:http://localhost:3000/things/new 
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36 

Request Payload 
------WebKitFormBoundary9vtTme67oAg1OMyL 
Content-Disposition: form-data; name="file"; filename="my_text.txt" 
Content-Type: text/plain 


------WebKitFormBoundary9vtTme67oAg1OMyL-- 

Как вы можете видеть, полезная нагрузка запроса содержит только файл, а не ключ. Возможно, файл должен появиться после всех остальных полей в запросе на отправку, и поэтому S3 не видит ключевое поле, как это предлагается в этом answer.

Некоторые соответствующие драгоценные камни:

* jquery-rails (4.0.4) 
* rails (4.2.3) 
* aws-sdk (2.1.13) 
* aws-sdk-core (2.1.13) 
* aws-sdk-resources (2.1.13) 

Также с помощью jquery.fileupload.js 5.42.3

Я не не знаю, как получить эту работу.

Заранее благодарен!

ответ

1

Я смог найти решение.

виде данных, которые были сгенерированных на внутреннем интерфейсе, что я захваченной на внешнем интерфейсе:

upload_form_data = '<%= escape_javascript(@presigned_post.fields.to_json.html_safe) %>' 

должны были быть преобразованы из строки JSON в объект JavaScript:

upload_form_data_obj = JSON.parse(upload_form_data); 

По-видимому, $.fileupload Функция ожидает объект для formData, а не строку.

С этой заменой необходимые данные формы включаются в POST на S3, и это успешно.

Вот рабочий код Javascript:

$(function() { 
var $form = $('form.direct_upload'), 
    upload_url, 
    upload_form_data, 
    upload_form_data_obj; 

if ($form.length) { 
    upload_url = '<%= escape_javascript(@presigned_post.url) %>' 
    upload_form_data = '<%= escape_javascript(@presigned_post.fields.to_json.html_safe) %>'; 
    upload_form_data_obj = JSON.parse(upload_form_data); 

    console.log('URL: ', upload_url); 
    console.log('Form data: ', upload_form_data_obj); 

    $form.find('input[type=file]').each(function(index, input) { 
    var $file_field = $(input); 
    $file_field.fileupload({ 
     fileInput: $file_field, 
     url: upload_url, 
     type: 'POST', 
     autoUpload: false, 
     formData: upload_form_data_obj, // needed to be an object, not a string 
     paramName: 'file', 
     dataType: 'JSON', 

     add: function(e, data) { 
     console.log('add callback fired.'); 
     $form.submit(function(e) { 
      e.preventDefault(); 

      console.log('form submitted.'); 
      console.log(data); 
      data.submit(); 
     }); 
     }, 
     start: function(e) { 
     console.log('start callback fired'); 
     }, 
     done: function(e, data) { 
     console.log('done callback fired'); 
     }, 
     fail: function(e, data) { 
     console.log('fail callback fired'); 
     console.log(e); 
     console.log(data); 
     } 
    }); 
    }); 
} 
}); 
Смежные вопросы