Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PUT file with AWS presignedURL

I'm creating presigned URL with aws-sdk-php for uploading files to S3 bucket. URLs for GET are working.

Here is the code

$client = S3Client::factory(array('region' => 'eu-west-1','key' => 'xxx','secret' => 'xxx',));

//option 1
$command = $client->getCommand('PutObject', array(
    'Bucket' => 'myBucket',
    'Key' => 'testing/signedPHP1_'.time(),
    'ContentType' => 'image/jpeg',
    'Body' => 'dump' //it's mandatory
));
$signedUrl = $command->createPresignedUrl('+5 minutes');
$signedUrl .= '&Content-Type=image%2Fjpeg';
echo("\n\nThe URL is: ". $signedUrl . "\n");
echo("Now run from console for upload:\ncurl -v -H \"Content-Type: image/jpeg\" -T /tmp/temp.jpg '" . $signedUrl . "'");

//option 2
$request = $client->put('myBucket/testing/signedPHP2_'.time());
$signedUrl = $client->createPresignedUrl($request, '+5 minutes');
$signedUrl .= '&Content-Type=image%2Fjpeg';
echo("\n\nThe URL is: ". $signedUrl . "\n");
echo("Now run from console for upload:\ncurl -v -H \"Content-Type: image/jpeg\" -T /tmp/temp.jpg '" . $signedUrl . "'");

//GET which works
$request = $client->get('myBucket/testing/existingFile.txt');
$signedUrl = $client->createPresignedUrl($request, '+5 minutes');
echo("\n\nThe URL is: ". $signedUrl . "\n");
echo("Now run:\ncurl '" . $signedUrl . "'");

//GET which works
$command = $client->getCommand('GetObject', array('Bucket' => 'myBucket','Key' => 'upload/data.txt'));
$signedUrl = $command->createPresignedUrl('+5 minutes');
echo("\n\nThe URL is: ". $signedUrl . "\n");
echo("Now run:\ncurl '" . $signedUrl . "'");

When trying to use curl command I'm getting and error SignatureDoesNotMatch with message The request signature we calculated does not match the signature you provided. Check your key and signing method.

The similar code in aws-sdk for Javascript is working

var AWS = require('aws-sdk');
AWS.config.update({ accessKeyId: 'xxx', secretAccessKey: 'xxx', region: 'eu-west-1' });
var s3 = new AWS.S3();
var params = {
    Bucket: 'myBucket',
    Key: 'testing/preSignedURLnodeJS_' + (+new Date),
    ContentType: 'image/jpeg',
    Expires: 60 * 5
};
s3.getSignedUrl('putObject', params, function(err, url) {
    console.log('The URL is: ', url);
    console.log('Now run from console for upload:\n\ncurl -v -H "Content-Type: image/jpeg" -T /tmp/temp.jpg \'' + url + '\'');
});

Already done a lot of researching but no results. What am I doing wrong?

like image 714
AnzeR Avatar asked Feb 17 '14 16:02

AnzeR


People also ask

How do I upload a signed URL to a file?

When you create a presigned URL, you must provide your security credentials and then specify a bucket name, an object key, an HTTP method (PUT for uploading objects), and an expiration date and time. The presigned URLs are valid only for the specified duration.

How do I upload files to AWS?

Sign in to the AWS Management Console and open the Amazon S3 console at https://console.aws.amazon.com/s3/ . In the Buckets list, choose the name of the bucket that you want to upload your folders or files to. Choose Upload.

What is Presigned URL in AWS?

A pre-signed URL allows you to grant temporary access to users who don't have permission to directly run AWS operations in your account. A pre-signed URL is signed with your credentials and can be used by any user. Presign.


2 Answers

On Github I got an answer from SDK developer and it says

$command = $client->getCommand('PutObject', array(
    'Bucket' => 'myBucket',
    'Key' => 'testing/signedPHP1_'.time(),
    'ContentType' => 'image/jpeg',
    'Body'        => '',
    'ContentMD5'  => false
));

Reference: https://github.com/aws/aws-sdk-php/issues/239

like image 91
AnzeR Avatar answered Oct 01 '22 17:10

AnzeR


Here you go:

First, create the presigned URLs

<?php 
/**
 *  getDocumentUploadUrls
 *
 *  creates a list of url so you can upload multiple files per
 *  document. After uploading the files it is requires for you
 *  to confirm the uploads.
 *
 *  @param Int $documentId the document id
 *  @param Int $count the amount of files you want to upload for this document
 *
 *  @return Array list of URLs to use with PUT request to upload files to s3.
 */
public function getDocumentUploadUrls(Int $documentId, Int $count = 1): Array
{
    $awsService = $this->getService('aws');

    $s3Client = $awsService->getSdk()->createS3();

    $urls = array_fill(1, $count, null);

    $bucket = 'yourbucket';

    $result = [];

    $expire = '+20 minutes';

    for ($i = 0; $i < $count; $i++) {
        $fileCount = $i + 1;

        $key = "{$documentId}/{$fileCount}";

        $cmd = $s3Client->getCommand('PutObject', [
            'Bucket' => $bucket,
            'Key'    => $key
        ]);

        $request = $s3Client->createPresignedRequest($cmd, $expire);

        $result[] = [
            'url' => (string) $request->getUri(),
            'reference' => "{$bucket}/{$key}"
        ];
    }

    return $result;
}

The result may look similar to this:

$result = [
    0 => [
        'url' => 'AwsS3://put/uri',
        'reference' => 'docId/count'
    ]
];

Now, to upload with php cURL:

if ($request->isPost()) {
        $files = $request->getFiles()->toArray();

        $files = reset($files);

        $data = $request->getPost();

        $docId = $data['documentId']; // get this from your frontend POST params

        $docCount = count($files);

        try {
            $endpoints = $this->getDocumentUploadUrls($userId, $docId, $docCount);

            $uploadInfo = $this->uploadDocuments($endpoints, $files);
        } catch (\Exception $e) {
            // handle exception
        }
}

public function uploadDocuments(Array $endpoints, Array $files)
{
    $info = [];

    foreach ($files as $index => $file) {
        $url = $endpoints[$index]['url']; // the no. of files must match the number of endpoints for this to work

        $type = isset($file['type']) ? $file['type'] : 'application/octet-stream';

        $headers = [
            "Content-Type: $type",
            'Access-Control-Allow-Origin: *',
        ];

        $ch = curl_init($url);

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
        curl_setopt($ch, CURLOPT_POSTFIELDS, $file);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        $response = curl_exec($ch);

        if (curl_errno($ch)) {
            throw new \Exception(curl_error($ch));
        }

        curl_close($ch);

        $info[] = [
            'headers' => $headers,
            'response' => $response
        ];
    }

    return $info;
}
like image 41
Gus Avatar answered Oct 02 '22 17:10

Gus