Home » Php » ajax – Upload multiple files directly to AWS S3 bucket from browser with PHP

ajax – Upload multiple files directly to AWS S3 bucket from browser with PHP

Posted by: admin July 12, 2020 Leave a comment

Questions:

I need to make a web form capable of uploading multiple files directly to my AWS S3 bucket from browser with PHP.

I came across this nice solution for single file uploads (https://www.sanwebe.com/2015/09/direct-upload-to-amazon-aws-s3-using-php-html):

<?php
$access_key         = "iam-user-access-key"; //Access Key
$secret_key         = "iam-user-secret-key"; //Secret Key
$my_bucket          = "mybucket"; //bucket name
$region             = "us-east-1"; //bucket region
$success_redirect   = 'http://'. $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']; //URL to which the client is redirected upon success (currently self) 
$allowd_file_size   = "1048579"; //1 MB allowed Size

//dates
$short_date         = gmdate('Ymd'); //short date
$iso_date           = gmdate("Ymd\THis\Z"); //iso format date
$expiration_date    = gmdate('Y-m-d\TG:i:s\Z', strtotime('+1 hours')); //policy expiration 1 hour from now

//POST Policy required in order to control what is allowed in the request
//For more info http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
$policy = utf8_encode(json_encode(array(
                    'expiration' => $expiration_date,  
                    'conditions' => array(
                        array('acl' => 'public-read'),  
                        array('bucket' => $my_bucket), 
                        array('success_action_redirect' => $success_redirect),
                        array('starts-with', '$key', ''),
                        array('content-length-range', '1', $allowd_file_size), 
                        array('x-amz-credential' => $access_key.'/'.$short_date.'/'.$region.'/s3/aws4_request'),
                        array('x-amz-algorithm' => 'AWS4-HMAC-SHA256'),
                        array('X-amz-date' => $iso_date)
                        )))); 

//Signature calculation (AWS Signature Version 4)   
//For more info http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html  
$kDate = hash_hmac('sha256', $short_date, 'AWS4' . $secret_key, true);
$kRegion = hash_hmac('sha256', $region, $kDate, true);
$kService = hash_hmac('sha256', "s3", $kRegion, true);
$kSigning = hash_hmac('sha256', "aws4_request", $kService, true);
$signature = hash_hmac('sha256', base64_encode($policy), $kSigning);
?>
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Aws S3 Direct File Uploader</title>
</head>
<body>
<form action="http://<?= $my_bucket ?>.s3.amazonaws.com/" method="post" enctype="multipart/form-data">
<input type="hidden" name="key" value="${filename}" />
<input type="hidden" name="acl" value="public-read" />
<input type="hidden" name="X-Amz-Credential" value="<?= $access_key; ?>/<?= $short_date; ?>/<?= $region; ?>/s3/aws4_request" />
<input type="hidden" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" />
<input type="hidden" name="X-Amz-Date" value="<?=$iso_date ; ?>" />
<input type="hidden" name="Policy" value="<?=base64_encode($policy); ?>" />
<input type="hidden" name="X-Amz-Signature" value="<?=$signature ?>" />
<input type="hidden" name="success_action_redirect" value="<?= $success_redirect ?>" /> 
<input type="file" name="file" />
<input type="submit" value="Upload File" />
</form>
<?php
//After success redirection from AWS S3
if(isset($_GET["key"]))
{
    $filename = $_GET["key"];
    $ext = pathinfo($filename, PATHINFO_EXTENSION);
    if(in_array($ext, array("jpg", "png", "gif", "jpeg"))){
        echo '<hr />Image File Uploaded : <br /><img src="//'.$my_bucket.'.s3.amazonaws.com/'.$_GET["key"].'" style="width:100%;" />';
    }else{
        echo '<hr />File Uploaded : <br /><a href="http://'.$my_bucket.'.s3.amazonaws.com/'.$_GET["key"].'">'.$filename.'</a>';
    }
}
?>
</body>
</html>

It works great for it purpose, but I need a solution that will be able to upload multiple uploads at once.

One of the comments on page specifies an approach:

AWS only allows you to upload one file at a time if uploading directly to S3. You can do multi file uploads by setting the file input to “multiple” and looping through each of the files, making mulitple submissions via AJAX. To do this you need to set up CORS on the bucket you want to upload to, otherwise you’ll be denied on the grounds of it being a cross-site script. It can be accomplished, as I’ve just got it working on my own project.

I am trying to follow, but not sure how exactly he proposes to use an AJAX to make it work. Does the form will be on AJAx request page and I just feed file names to it?

Can someone familiar with an issue can please explain it to me more thoroughly or direct me to the alternative solution(s)?

How to&Answers:

you can encode files to base64 string formats using btoa() and send it to your server as an array
[{"base64encodedFile 1",....,"base64encodedFile n"}]
in one ajax request. on your server you can loop through the array, decode them back and send it to s3 one by one as an input stream.
I have done this in java, and it worked fine.

ajax :

$.ajax({
    type: "POST",
    url: "/upload",
    data: [{"base64encodedFile 1",....,"base64encodedFile n"}],
    dataType: "json",
    success: function( data, textStatus, jqXHR) {
      // uploaded
    },
    error: function(jqXHR, textStatus, errorThrown){
      // error
    }
});

server side pseudo code:

foreach(item in array) {
    decodedFile = base64Decode(item)
    InputStream fis = new ByteArrayInputStream(decodedFile)
    setMetaData() // such as content length, max-age , ...
    PutObjectRequest pro = new PutObjectRequest(getBUCKET_NAME(), 
    fileName, fis, metadata)
    por.setCannedAcl(CannedAccessControlList.PublicRead)
    s3Client.putObject(por)
}

Answer:

You should use multiple="multiple" and also use name="file[]".

Try using:

<form action="http://<?= $my_bucket ?>.s3.amazonaws.com/" method="post" enctype="multipart/form-data">
    <input type="hidden" name="key" value="${filename}" />
    <input type="hidden" name="acl" value="public-read" />
    <input type="hidden" name="X-Amz-Credential" value="<?= $access_key; ?>/<?= $short_date; ?>/<?= $region; ?>/s3/aws4_request" />
    <input type="hidden" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" />
    <input type="hidden" name="X-Amz-Date" value="<?=$iso_date ; ?>" />
    <input type="hidden" name="Policy" value="<?=base64_encode($policy); ?>" />
    <input type="hidden" name="X-Amz-Signature" value="<?=$signature ?>" />
    <input type="hidden" name="success_action_redirect" value="<?= $success_redirect; ?>" /> 
    <input type="file" name="file[]" multiple="multiple" />
    <input type="submit" value="Upload File" />
</form>