How can I upload files to Amazon S3 using Cordova

2020-03-25 12:28发布

问题:

I'm following Heroku's tutorial on direct uploads to Amazon S3. After getting a signed request from AWS through the Node.js app, they use a "normal" XMLHttpRequest to send the file.

This is their function:

function upload_file(file, signed_request, url){
    var xhr = new XMLHttpRequest();
    xhr.open("PUT", signed_request);
    xhr.setRequestHeader('x-amz-acl', 'public-read');
    xhr.onload = function() {
        if (xhr.status === 200) {
            document.getElementById("preview").src = url;
            document.getElementById("avatar_url").value = url;
        }
    };
    xhr.onerror = function() {
        alert("Could not upload file.");
    };
    xhr.send(file);
}

Now, I'm working with Cordova and, since I don't get a File object from the camera plugin, but only the file URI, I used Cordova's FileTransfer to upload pictures to my Node.js app with multipart/form-data and it worked fine.

However, I can't manage to make it work for Amazon S3.

Here's what I have:

$scope.uploadPhoto = function () {
    $scope.getSignedRequest(function (signedRequest) {
        if (!signedRequest)
            return;

        var options = new FileUploadOptions();
        options.fileKey = 'file';
        options.httpMethod = 'PUT';
        options.mimeType = 'image/jpeg';
        options.headers = {
            'x-amz-acl': 'public-read'
        };
        options.chunkedMode = false;

        var ft = new FileTransfer();
        ft.upload($scope.photoURI, encodeURI(signedRequest.signed_request), function () {
            // success
        }, function () {
            // error
        }, options);
    });
};

I've tried both chunkedMode = true and chunkedMode = false, but neither the success nor the error callback is called.

So, is there a way to upload a file to S3 with FileTransfer? Do I actually need the signed request or is it only necessary if I use XHR?

Any hint is appreciated.

回答1:

I ended up with this function in Cordova:

$scope.uploadPhoto = function () {
    $scope.getSignedRequest(function (signedRequest) {
        if (!signedRequest)
            return;

        var options = new FileUploadOptions();
        options.chunkedMode = false;
        options.httpMethod = 'PUT';
        options.headers = {
            'Content-Type': 'image/jpeg',
            'X-Amz-Acl': 'public-read'
        };

        var ft = new FileTransfer();
        ft.upload($scope.photoURI, signedRequest.signedUrl, function () {
            $scope.$apply(function () {
                // success
            });
        }, function () {
            $scope.$apply(function () {
                // failure
            });
        }, options);
    });
};

The important bits are setting the Content-Type header, so that multipart/form-data won't be used, and chunkedMode = false to send the file with a single request.

EDIT: Removed changes to the plugin code which were, in hindsight, useless (outdated plugin).



回答2:

Not able to add comment: Strange. Works for me using $cordovaFileTransfer.upload. I don't have the 'x-amz-acl': 'public-read' header. Also I don't use encodeURI on the signed url. Have you been able to debug it? See any errors? I used chrome://inspect and port forwarding to connect to my app running on the phone, so I was able to debug the response from Amazon. Might be another reason why it's failing.