PUT / Copy с PHP, REST, Flex и Amazon S3 - PullRequest
4 голосов
/ 26 марта 2011

Я несколько недель пытался правильно отформатировать REST request в Amazon AWS S3 API, используя доступные примеры в Интернете, но не смог даже успешно подключиться.

Я нашел код для генерации подписи, нашел правильный метод форматирования «строки для кодирования» и http headers. Я прошел через ошибки signatureDoesNotMatch, чтобы получить сообщение Anonymous users can not perform copy functions, Please authenticate.

У меня есть рабочая копия приложения Adobe Flex, которое успешно загружает файлы, но с их «оригинальным» именем файла. Смысл использования REST с Amazon API состоит в том, чтобы выполнить PUT (копию) файла, чтобы я мог переименовать его в то, что может использовать моя внутренняя система.

Если бы я мог найти способ заставить это REST работать, или, возможно, способ указать "новое" имя файла в Flex при загрузке, я мог бы избежать всей этой ситуации REST все вместе.

Если кто-то успешно выполнил команду PUT/Copy на Amazon API через REST, мне было бы очень интересно узнать, как это было достигнуто - ИЛИ - если бы кто-нибудь смог изменить имя файла назначения с помощью Flex fileReference.browse() Метод Я также буду вечно благодарен за любые указатели.


PHP-код для этого выглядит следующим образом:

$aws_key = 'removed_for_security';
$aws_secret = 'removed_for_security';
$source_file = $uploaded_s3_file; // file to upload to S3 (defined in above script)
$aws_bucket = 'bucket'; // AWS bucket
$aws_object = $event_file_name; // AWS object name (file name)
if (strlen($aws_secret) != 40) die("$aws_secret should be exactly 40 bytes long");
$file_data = file_get_contents($source_file);
if ($file_data == false) die("Failed to read file " . $source_file);

// opening HTTP connection to Amazon S3
$fp = fsockopen("s3.amazonaws.com", 80, $errno, $errstr, 30);
if (!$fp) die("$errstr ($errno)\n");

// Uploading object
$file_length = strlen($file_data); // for Content-Length HTTP field
$dt = gmdate('r'); // GMT based timestamp

// preparing String to Sign (see AWS S3 Developer Guide)
// preparing string to sign
$string2sign = "PUT


{$dt}
/{$aws_bucket}/{$aws_object}";

// preparing HTTP query 
// $query = "PUT /".$aws_bucket."/".$event_file_name." HTTP/1.1
$query = "PUT /" . $event_file_name . " HTTP/1.1
Host: {$aws_bucket}.s3.amazonaws.com
Date: {$dt}
x-amz-copy-source: /{$aws_bucket}/{$current_s3_filename}
x-amz-acl: public-read

Authorization: AWS {$aws_key}:" . amazon_hmac($string2sign) . "\n\n";

$query .= $file_data;
$resp = sendREST($fp, $query);
if (strpos($resp, '') !== false) {
     die($resp);
}
echo "FILE uploaded\n";

// done
echo "Your file's URL is: http://s3.amazonaws.com/{$aws_bucket}/{$aws_object}\n";
fclose($fp);

// Sending HTTP query and receiving, with trivial keep-alive support
function sendREST($fp, $q, $debug = true){
     if ($debug) echo "\nQUERY<<{$q}>>\n";
     fwrite($fp, $q);
     $r = '';
     $check_header = true;
     while (!feof($fp)) {
          $tr = fgets($fp, 256);
          if ($debug) echo "\nRESPONSE<<{$tr}>>";
          $r .= $tr;
          if (($check_header) && (strpos($r, "\r\n\r\n") !== false)) {

               // if content-length == 0, return query result
               if (strpos($r, 'Content-Length: 0') !== false) {
                    return $r;
               }
          }

          // Keep-alive responses does not return EOF
          // they end with \r\n0\r\n\r\n string
          if (substr($r, -7) == "\r\n0\r\n\r\n") {
               return $r;
          }
     }
     return $r;
}

// hmac-sha1 code START
// hmac-sha1 function: assuming key is global $aws_secret 40 bytes long
// read more at http://en.wikipedia.org/wiki/HMAC
// warning: key($aws_secret) is padded to 64 bytes with 0x0 after first function call
function amazon_hmac($stringToSign) {

     // helper function binsha1 for amazon_hmac (returns binary value of sha1 hash)
     if (!function_exists('binsha1')) {
          if (version_compare(phpversion(), "5.0.0", ">=")) {
               function binsha1($d) { return sha1($d, true); }
          } else {
               function binsha1($d) { return pack('H*', sha1($d)); }
          }
     }
     global $aws_secret;
     if (strlen($aws_secret) == 40) {
          $aws_secret = $aws_secret . str_repeat(chr(0), 24);
     }
     $ipad = str_repeat(chr(0x36), 64);
     $opad = str_repeat(chr(0x5c), 64);
     $hmac = binsha1(($aws_secret ^ $opad) . binsha1(($aws_secret ^ $ipad) . $stringToSign));
     return base64_encode($hmac);
}
// hmac-sha1 code END

Когда я отправляю неверный или неправильный заголовок, я получаю соответствующее сообщение об ошибке, как и ожидалось:

Запрос:

PUT /bucket/1-132-1301047200-1.jpg HTTP / 1.1 Хост: s3.amazonaws.com x-amz-acl: public-read Соединение: keep-alive Длина содержимого: 34102 Дата: сб, 26 Март 2011 00:43:36 +0000 Авторизация: AWS -снято для безопасности-: GmgRObHEFuirWPwaqRgdKiQK / EQ =

HTTP / 1.1 403 Запрещено
x-amz-request-id: A7CB0311812CD721
x-amz-id-2: ZUY0mH4Q20Izgt / 9BNhpJl9OoOCp59DKxlH2JJ6K + sksyxI8lFtmJrJOk1imxM / A
Тип содержимого: application / xml
Передача-кодировка: чанки
Дата: сб, 26 марта 2011 г. 00:43:36 GMT
Подключение: закрыть
Сервер: AmazonS3
397 SignatureDoesNotMatchСчитанная нами подпись запроса не соответствует предоставленной вами подписи. Проверьте свой ключ и метод подписи. 50 55 54 0a 0a 0a 53 61 74 2c 20 32 36 20 4d 61 72 20 32 30 31 31 20 30 30 3a 34 33 3a 33 36 20 2b 30 30 30 30 0a 78 2d 61 6d 7a 2d 61 63 6c 3a 70 75 62 6c 69 63 2d 72 65 61 64 0a 2f 6d 6c 68 2d 70 72 6f 64 75 63 74 69 6f 6e 2f 31 2d 31 33 32 2d 31 33 30 31 30 34 37 32 30 30 2d 31 2e 6а 70 67A7CB0311812CD721ZUY0mH4Q20Izgt / 9BNhpJl9OoOCp59DKxlH2JJ6K + sksyxI8lFtmJrJOk1imxM / AGmgRObHEFuirWPwaqRgdKiQK / EQ = PUT Сб, 26 Mar 2011 00:43:36 +0000 х-AMZ-ACL: общественные чтения /bucket/1-132-1301047200-1.jpg-removed для безопасности - 0

но при отправке правильно отформатированных запросов он говорит, что я не аутентифицирован:

Используемый запрос:

PUT /1-132-1301047200-1.jpg HTTP / 1.1 Хост: bucket.s3.amazonaws.com Дата: суббота, 26 марта 2011 г. 00:41:50 +0000 x-amz-copy-source: / bucket /clock.jpg x-amz-acl: публичное чтение. Авторизация: AWS -снято для безопасности-: BMiGhgbFnVAJyiderKjn1cT7cj4 =

HTTP / 1.1 403 Запрещено
x-amz-request-id: ABE45FD4DFD19927
x-amz-id-2: CnkMmoF550H1zBlrwwKfN8zoOSt7r / zud8mRuLqzzBrdGguotcvrpZ3aU4HR4RoO
Тип содержимого: application / xml
Передача-кодировка: чанки
Дата: сб, 26 марта 2011 г. 00:41:50 GMT
Сервер: AmazonS3

AccessDenied
Анонимные пользователи не могут копировать объекты. Пожалуйста, подтвердите
ABE45FD4DFD19927CnkMmoF550H1zBlrwwKfN8zoOSt7r / zud8mRuLqzzBrdGguotcvrpZ3aU4HR4RoO 0
Дата: сб, 26 марта 2011 г. 00:41:50 GMT
Подключение: закрыть
Сервер: AmazonS3

1 Ответ

8 голосов
/ 26 марта 2011

В течение нескольких недель я пытался правильно отформатировать REST-запрос к API Amazon AWS S3, используя доступные примеры в Интернете

Вы пробовали Amazon AWS SDK для PHP ? Это всеобъемлющее, полное и, самое главное, написанное Amazon. Если их собственный код не работает для вас, что-то будет действительно не так.


Вот пример кода, использующего связанный SDK для загрузки example.txt в текущем каталоге в корзину с именем 'my_very_first_bucket'.

<?php
// Complain wildly.
    ini_set('display_errors', true);
    error_reporting(-1);
// Set these yourself.
    define('AWS_KEY', '');
    define('AWS_SECRET_KEY', '');
// We'll assume that the SDK is in our current directory
    include_once 'sdk-1.3.1/sdk.class.php';
    include_once 'sdk-1.3.1/services/s3.class.php';
// Set the bucket and name of the file we're sending.
// It happens that we're actually uploading the file and 
// keeping the name, so we're re-using the variable
// below.
    $bucket_name = 'my_very_first_bucket';
    $file_to_upload = 'example.txt';
// Fire up the object
    $s3 = new AmazonS3(AWS_KEY, AWS_SECRET_KEY);
// This returns a "CFResponse"
    $r = $s3->create_object(
        $bucket_name,
        $file_to_upload,
        array(
        // Filename of the thing we're uploading
            'fileUpload' => (__DIR__ . '/' . $file_to_upload),
        // ACL'd public.
            'acl' => AmazonS3::ACL_PUBLIC,
        // No wai.
            'contentType' => 'text/plain',
        // The docs say it'll guess this, but may as well.
            'length' => filesize(__DIR__ . '/' . $file_to_upload)
        )
    );
// Did it work?
    echo "Worked: ";
    var_dump($r->isOK());
// Status as in HTTP.
    echo "\nStatus: ";
    var_dump($r->status);
// The public URL by which we can reach this object.
    echo "\nURL: ";
    echo $s3->get_object_url($bucket_name, $file_to_upload);
// Tada!
    echo "\n";

Соответствующие документы API:

Вы можете перемещаться по остальным методам в левом меню. Он довольно полный, включая создание нового сегмента, управление, удаление, то же самое для объектов и т. Д.

Вы должны быть в состоянии вставить это в свой код и заставить его работать должным образом. PHP 5.2-safe.


Редактировать Серебряный Тигр:

Чарльз -

Вы предоставляете метод, использующий функции API SDK для загрузки файла из локальной файловой системы в корзину по моему выбору. У меня эта часть уже работает через Flex и загрузка работает как шарм. Проблема заключается в том, что я могу отправить запрос REST на AWS S3, чтобы изменить имя файла с его текущего «загруженного» имени на новое имя с более подходящим именем, которое будет работать с моим бэкэндом (база данных, отслеживание и т.д. обрабатывать и отображать отдельно в PHP с MyySQL).

AWS S3 на самом деле не поддерживает функцию «копирования», поэтому они предоставили метод для повторного «PUT» файла путем чтения источника из вашего собственного сегмента и размещения новой копии с другим именем в том же блоке. Трудность, с которой я столкнулся, заключается в обработке запроса REST и, следовательно, шифровании HMAC.

Я ценю ваше время и понимаю приведенный вами пример, так как у меня также есть рабочая копия загрузки PHP, которая работала до того, как я разработал приложение Flex. Причиной для Flex стало включение обновлений состояния и динамически обновляемого индикатора выполнения, который также работает как чудо:).

Я буду продолжать использовать решение REST, поскольку с точки зрения Amason zupport это будет единственный способ переименовать файл, уже существующий в моем сегменте, для каждой группы поддержки.

Как всегда, если у вас есть пожелания или предложения относительно представления REST, я был бы признателен за любые отзывы.

Спасибо,

Серебряный тигр


Подтверждение копирования / удаления работ:

    $r = $s3->copy_object(
        array( 'bucket' => $bucket_name, 'filename' => $file_to_upload ),
        array( 'bucket' => $bucket_name, 'filename' => 'foo.txt' )
    );
// Did it work?
    echo "Worked: ";
    var_dump($r->isOK());
// Status as in HTTP.
    echo "\nStatus: ";
    var_dump($r->status);

// The public URL by which we can reach this object.
    echo "\nURL: ";
    echo $s3->get_object_url($bucket_name, 'foo.txt');

    echo "\nDelete: ";
// Nuke?
    $r = $s3->delete_object($bucket_name, $file_to_upload);
// Did it work?
    echo "Worked: ";
    var_dump($r->isOK());
// Status as in HTTP.
    echo "\nStatus: ";
    var_dump($r->status);

Редактировать Серебряный Тигр:

Чарльз -

Нет необходимости в REST, нет проблем ... SDK 1.3.1 и ваша помощь решили проблему. код, который я использовал для тестирования, очень похож на ваш:

// Complain wildly.
    ini_set('display_errors', true);
    error_reporting(-1);
// Set these yourself.
    define('AWS_KEY', 'removed for security');
    define('AWS_SECRET_KEY', 'removed for security');
// We'll assume that the SDK is in our current directory
    include_once 'includes/sdk-1.3.1/sdk.class.php';
    include_once 'includes/sdk-1.3.1/services/s3.class.php';
// Set the bucket and name of the file we're sending.
// It happens that we're actually uploading the file and 
// keeping the name, so we're re-using the variable
// below.
    $bucket = 'bucket';
    $file_to_upload = 'example.txt';
    $Source_file_to_copy = 'Album.jpg';
    $Destination_file = 'Album2.jpg';
// Fire up the object
// Instantiate the class
$s3 = new AmazonS3();
$response = $s3->copy_object(
    array( // Source
        'bucket' => $bucket,
        'filename' => $Source_file_to_copy
    ),
    array( // Destination
        'bucket' => $bucket,
        'filename' => $Destination_file
    )
);
// Success?
var_dump($response->isOK());

Теперь я выполню удаление после копирования, и мы золотые. Спасибо, сэр, за понимание и помощь.

Серебряный тигр

...