Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Blueimp File Upload - Multiple Uploads Directly to S3

After searching the past couple days, I've found nearly 30 different people asking this same question, and I haven't found an answer. A couple reported that they found a solution, but did not provide it, so I'm hoping someone could answer it here.


Using blueimp jQuery File Upload, how can you upload multiple files directly to Amazon S3?

Problem: S3 accepts only one file per request.

Solution: Use blueimp jQuery File Upload to send separate requests for each file.

Roadblock: I cannot figure out how to make blueimp jQuery File Upload do this.


Solution Attempt

The guide is here: https://github.com/blueimp/jQuery-File-Upload/wiki/Upload-directly-to-S3

S3 currently requires more form fields than the ones shown in the guide. Here's a trimmed down version of my code:

$(':file').each(function(i, el) {

    var fileInput = $(el);
    var form = fileInput.parents('form:first');

    fileInput.fileupload({
        forceIframeTransport: true,
        autoUpload: true,
        singleFileUploads: true, //default anyway
        add: function(event, data) {
            var files = data.files || [];
            var nextInQueue = files[0]; //this is a queue, not a list
            console.log('nextInQueue:', nextInQueue);
            if (!nextInQueue) return;

            var fileData = {name: nextInQueue.name, mime: nextInQueue.type, size: nextInQueue.size};

            $.ajax({
                url: '/s3stuff',
                type: 'POST',
                dataType: 'json',
                data: {'file': fileData},
                async: false,
                success: function(res) {
                    form.find('input[name="key"]').val(res.key);
                    form.find('input[name="AWSAccessKeyId"]').val(res.AWSAccessKeyId);
                    form.find('input[name="policy"]').val(res.policy);
                    form.find('input[name="signature"]').val(res.signature);
                    form.find('input[name="acl"]').val(res.acl);
                    form.find('input[name="success_action_status"]').val(res.success_action_status);
                    form.find('input[name="Content-Type"]').val(nextInQueue.type);
                    form.attr('action', res.url);

                    data.submit(); //why is this submitting all files at once?
                }
            });
        },
        fail: function(err) {
            console.log('err:', err.stack);
        }
    });
});

Error

When I try to upload a single file, it works great! But when I try to upload multiple files, S3 returns this 400 error:

<?xml version="1.0" encoding="UTF-8"?>
<Error>
    <Code>InvalidArgument</Code>
    <Message>POST requires exactly one file upload per request.</Message>
    <ArgumentName>file</ArgumentName>
    <ArgumentValue>2</ArgumentValue>
</Error>

Analysis

The blueimp jQuery File Upload documentation says this (under add):

If the singleFileUploads option is enabled (which is the default), the add callback will be called once for each file in the selection for XHR file uploads, with a data.files array length of one, as each file is uploaded individually.

This is where I'm stuck:
If "each file is uploaded individually" to S3, why does S3 say otherwise?

Also, regardless of the number of files, the add function runs only once. (I verify this with the console.log statement.) I see 2 likely reasons for this:

  1. The plugin stops further submissions after one fails.
  2. The plugin is not submitting files individually (for whatever reason).

Is my code incorrect, am I missing an option in the plugin, or does the plugin not support multiple direct uploads to S3?


Update

Here's an html file for quick testing: http://pastebin.com/mUBgr4MP

like image 874
GreenRaccoon23 Avatar asked Jul 19 '16 20:07

GreenRaccoon23


1 Answers

Author of JQuery File Upload here.

The reason the files are not submitted individually is the forceIframeTransport option, which is set to true in your example.

The documentation for the singleFileUploads option states the following:

By default, each file of a selection is uploaded using an individual request for XHR type uploads.

While the documentation for the forceIframeTransport option states the following:

Set this option to true to force iframe transport uploads, even if the browser is capable of XHR file uploads.

So, the solution is to enable CORS on the S3 bucket and to not enable the forceIframeTransport option.

By the way, most of the integration examples are user contributions (including the S3 upload guides). Here's another one which uses the S3 CORS feature and might help your out (haven't tested it though): http://pjambet.github.io/blog/direct-upload-to-s3

like image 133
Sebastian Tschan Avatar answered Sep 23 '22 06:09

Sebastian Tschan