Искусственный жемчуг: Часть 2. Perl и «облачные вычисления» Amazon

Безопасная выгрузка данных в S3 через HTML-форму

Эта серия из пяти статей познакомит вас с созданием простого фотохостинга при помощи Perl и Apache c использованием служб Amazon Simple Storage Service (S3; Служба простого хранения) и SimpleDB. В этом разделе рассказывается о том, как выгружать файлы в S3 с web-страницы при помощи HTML-формы, чтобы минимизировать нагрузку на сервер и при этом соблюсти требования политики безопасности.

Теодор Златанов, программист, Gold Software Systems

Теодор Златанов (Teodor Zlatanov) получил диплом магистра по вычислительной технике в Boston University в 1999. Он работает программистом с 1992, используя Perl, Java, C, и C++. Он интересуется работами с открытым исходным кодом по синтаксическому анализу текста, трехуровневыми архитектурами клиент-серверных баз данных, системным администрированием UNIX, CORBA и управлением проектами.



04.08.2009

Выгрузить файл в службы Amazon S3 с web-страницы можно несколькими способами:

  • При помощи командной строки, используя соответствующие модули CPAN.
  • При помощи командной строки, используя соответствующие модули Amazon.
  • Непосредственно из HTML-формы.

Чтобы получить больше от этой серии статей

Эта серия требует начальных знаний о HTTP и HTML, а также знаний среднего уровня о JavaScript и Perl (в составе модуля Apache mod_perl). Вам пригодятся знания о реляционных базах данных, дисковых системах хранения и работе сети. Следующие статьи серии будут все в большей степени техническими, поэтому если вам необходима помощь в этих темах, обратитесь к разделу Ресурсы.

В этой статье рассматривается выгрузка файлов непосредственно из HTML-формы, что минимизирует нагрузку на сервер. Успешная выгрузка приводит к созданию URL, который мы будем использовать в дальнейших статьях этой серии для настройки других компонентов создаваемого нами фотохостинга. В качестве имени домена используется share.lifelogs.com.

Выгрузка в S3

Выгружать данные в S3 можно при помощи метода POST. Кроме того, можно использовать метод HTTP PUT HTTP и вызов SOAP PutObject. В этой статье мы будем использовать метод POST из-за его простоты, а также потому, что он не использует ресурсов сервера (дисковую подсистему, процессор и сетевое соединение).

Единственной крупной проблемой при выгрузке данных в S3 является невозможность изменения метаданных. Возможно, это следствие распределенной природы S3 или желания Amazon сохранить простоту S3. На форумах по S3 представители Amazon ответили, что в дальнейшем этот вопрос может быть решен.

В любом случае это означает, что Content-Type (Тип содержимого) должен быть указан при выгрузке данных, иначе будет указан непонятный тип содержимого binary/octet-stream. Другие метаданные не столь важны, потому что для отслеживания выгрузок будет использоваться Amazon SimpleDB, и метаданные можно хранить в базе данных. Мы предлагаем для решения проблемы с типом содержимого использовать успешное в большинстве случаев решение при помощи JavaScript.

Имя пользователя будет являться частью полученного URL. Можно поместить его в метаданные для выгрузки так, чтобы оно было постоянно связано с файлом, но в этом случае пользователь не сможет изменить указанное имя, пока не будет выполнена повторная выгрузка файла с обновленными метаданными. Можно хранить идентификаторы пользователя, связанные не с именем пользователя, а с объектом S3, но для решения нашей задачи – создания простого фотохостинга – в этом нет необходимости.

На этом этапе у нас (операторов web-сайта) есть блок S3 с именем images.share.lifelogs.com. Разрешения блока настроены для публичного доступа на чтение. Если у вас проблемы с настройкой при помощи инструкций от Amazon, используйте утилиты вроде S3Fox, JungleDisk или другие интерфейсы S3 для настройки блоков. В дальнейшей работе вам понадобятся доступ к web-службам Amazon и секретные ключи.

Для управления выгрузками будет использоваться политика. Политика является набором правил, выраженных в формате данных JSON. Для подписывания политики используется секретный ключ Amazon.

Подписывание политики осуществляется при помощи модуля Perl Digest::HMAC_SHA1. Сначала вам необходимо закодировать политику в Base64, удалить все символы перевода строки, подписать полученные данные, закодировать подпись в Base64, трижды повернуться на месте, взяться за пальцы на ноге, вырвать у себя волос и сжечь его, и, наконец, отправить 1 доллар 28 центов одноцентовыми монетами госпоже Элли Ковальски и ждать от нее ответного письма с готовой подписью. Шутка! Сжигать волос необязательно. Вместо этого попробуйте сделать следующее:

Листинг 1. Подписывание политики выгрузки
my $aws_secret_access_key = 'get it from 
 http://aws-portal.amazon.com/gp/aws/developer/account/index.html?action=access-key';

my $policy = 'здесь вставьте политику целиком';
$policy = encode_base64($policy);
$policy =~ s/\n//g;

my $signature = encode_base64(hmac_sha1($policy, $aws_secret_access_key));

$signature =~ s/\n//g;

Политика выгрузки S3

Мы создадим политику до создания формы; таким образом, задачи безопасности и удобства использования будут решены до написания HTML-кода, что всегда является хорошим тоном.

Содержимое политики является весьма простым.

Листинг 2. Содержимое политики выгрузки
{
 "expiration": "3000-01-01T00:00:00Z",
  "conditions": [ 
    {"bucket": "images.share.lifelogs.com"}, 
    {"acl": "public-read"},
    ["starts-with", "$key", ""],
    {"success_action_redirect": "http://share.lifelogs.com/s3uploaded/$user"},
    ["content-length-range", 0, 1048576]
  ]
}

О содержимом политики очень хорошо рассказано в документации для разработчиков S3 (ссылка приведена в разделе Ресурсы). Должно быть указано правильное имя блока, соответствующее значение списка контроля доступа (ACL), ключ может быть любым, а в случае успеха будет выполняться переход по указанному URL. Размер загружаемого файла не должен превышать 1 МБ. Обратите внимание на срок действия (подробнее о нем рассказано ниже).

Содержимое политики будет подписано и опубликовано для всеобщего обозрения, так что даже ваш злейший враг не сможет подделать ее содержимое. Это делает политику одним из элементов безопасности сайта и гарантирует, что будут выполняться только выгрузки в S3 на основании заданных параметров. Помните, это важно, потому что вы платите за использование S3.

Срок действия имеет значение 3000 (да, именно 3000-й год). Практически это означает, что политика действует всегда. Вместо этого можно ограничить срок действия вплоть до 10 минут, это гарантирует, что политика не будет использоваться заблокированными пользователями спустя 10 минут после их последнего авторизованного доступа. Однако это может привести к жалобам от пользователей, которым требуется более 10 минут на выгрузку файла, поскольку их сессии будут сбрасываться. Поэтому подумайте и назначьте подходящий срок действия политики, вместо того чтобы указывать его наугад.

Политика должна содержать условия для всех полей, указанных в форме. Это препятствует подделке и поддерживает полноту документов политики.

Теперь, когда мы настроили политику, давайте займемся формой выгрузки.


Форма выгрузки S3

Помните, мы говорили о метаданных Content-Type , связанных с объектами S3 и о том, что их необходимо указать до выгрузки объекта? К сожалению, это плохо сочетается с выгрузкой фотографий, потому что мы не знаем наверняка, что будут загружать пользователи. Например, изображения JPEG и PNG имеют разные типы содержимого (на самом деле они называются MIME-типами, и их количество исчисляется буквально сотнями).

Решением является встроенный сценарий JavaScript. Поскольку все размещается в одном скрипте Perl, необходимо избегать символов \ и $ (это немного запутывает содержимое). Исходный код JavaScript можно найти в сгенерированном коде HTML. Далее в этой серии статей мы будем использовать Template Toolkit (набор шаблонов), чтобы делать это правильно, но идея заключается в создании автономного и простого скрипта. Это очевидное решение, но, к сожалению, оно отрицательно сказывается на читабельности кода.

Листинг 3. s3form.pl
#!/usr/bin/perl

use warnings;
use strict;
use Data::Dumper;
use Digest::HMAC_SHA1 qw(hmac_sha1 hmac_sha1_hex);
use MIME::Base64;

my $aws_access_key_id     = 'get it from
 http://aws-portal.amazon.com/gp/aws/developer/account/index.html?action=access-key';
my $aws_secret_access_key = 'get it from 
 http://aws-portal.amazon.com/gp/aws/developer/account/index.html?action=access-key';

my $user = 'username'; # имя пользователя для выгрузки

my $policy = '{"expiration": "3000-01-01T00:00:00Z",
  "conditions": [ 
    {"bucket": "images.share.lifelogs.com"}, 
    {"acl": "public-read"},
    ["starts-with", "$key", ""],
    ["starts-with", "$Content-Type", ""],
    {"success_action_redirect": "http://share.lifelogs.com/s3uploaded/$user"},
    ["content-length-range", 0, 1048576]
  ]
}';

$policy = encode_base64($policy);
$policy =~ s/\n//g;

my $signature = encode_base64(hmac_sha1($policy, $aws_secret_access_key));

$signature =~ s/\n//g;
print <<EOHIPPUS;
<html> 
  <head>
    <title>S3 POST Form</title> 
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 
    <script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js"
                 type="text/javascript"></script>
  </head>

  <body> 
  <script language="JavaScript">
function submitUploaderForm()
{
 var form = \$('uploader'); // обратите внимание, что переходы мы выполняем из Perl
 var file = form['file'];
 var ct = form['Content-Type'];

 var filename = ''+\$F(file); // обратите внимание, что переходы мы выполняем из Perl
 var f = filename.toLowerCase(); // всегда сравнивайте с версией в нижнем регистре

 if (!navigator['mimeTypes'])
 {
  alert("Извините, браузер не может определить тип загружаемого файла.");
  return false;
 }

 var type = \$A(navigator.mimeTypes).detect(function(m)
 {
  // какое-либо из расширений подходит?
  // обратите внимание, что переходы мы выполняем из Perl
  return m.type.length > 3 && m.type.match('/') &&
       \$A(m.suffixes.split(',')).detect(function(suffix)
  {
    return f.match('\\.' + suffix.toLowerCase() + '\$'); 
      // обратите внимание, что переходы мы выполняем из Perl
  });
 });

 if (type && type['type'])
 {
  ct.value = type.type;
  return true;
 }

 alert("Извините, тип файла не определен " + filename);
 return false;
}
</script>
    <form id="uploader" action="https://images.share.lifelogs.com.s3.amazonaws.com/"
     method="post" enctype="multipart/form-data"
     onSubmit="return submitUploaderForm();">
      <input type="hidden" name="key" value="\${filename}">
      <input type="hidden" name="AWSAccessKeyId" value="$aws_access_key_id"> 
      <input type="hidden" name="acl" value="public-read">
      <input type="hidden" name="success_action_redirect"
       value="http://share.lifelogs.com/s3uploaded/$user">
      <input type="hidden" name="policy" value="$policy">
      <input type="hidden" name="Content-Type" value="image/jpeg">
      <input type="hidden" name="signature" value="$signature">
      Select File to upload to S3: 
      <input name="file" type="file"> 
      <br> 
      <input type="submit" value="Upload File to S3"> 
    </form> 
  </body>
</html>
EOHIPPUS

Прошу прощения у всех, кого раздражает неэлегантный код JavaScript, зато он должен работать в большинстве современных браузеров. По существу мы перехватываем нажатие кнопки подтверждения и возвращаем положительный результат только в том случае, если тип загружаемого файла определен.

Примечание редактора: вы можете загрузить этот скрипт. Две строки, выделенные полужирным шрифтом в листинге 3, в действительности не должны быть разорваны, но ширина наших мониторов ограничена. Если вы копируете содержимое скрипта из статьи, удалите лишние разрывы строк. В скрипте, приведенном по ссылке, эти строки изначально указаны корректно.


Форма выгрузки: JavaScript и комментарии

Используйте среду разработки Prototype с сайта Google API (см. Ресурсы). Вы можете разместить ее у себя, если вы параноик, страдаете навязчивыми идеями или любите покомандовать (но помните, «если у вас паранойя, это еще не значит, что за вами не следят ОНИ»).

Получите имя файла из формы при помощи утилит Prototype, затем определите расширение (в нижнем регистре). Для каждого MIME-типа, известного браузеру, используется метод обработки массива Prototype detect(), который ищет первое соответствие следующим условиям:

  • Тип должен быть длиннее трех символов.
  • Он должен содержать символ /.
  • Расширение файла должно соответствовать какому-либо из расширений MIME-типов.

Проверка на длину более трех символов и наличие символа / фактически нужны для Firefox, в котором существует MIME-тип "*", подходящий подо что угодно. Поскольку это неприемлемо, нам необходима возможность пропустить его и (мы надеемся!) найти любой другой подходящий MIME-тип.

Перебор расширений выполняется при помощи метода обработки строк JavaScript split(), который создает массив элементов. Так, если расширениями являются jpg,jpeg, то производится перебор двух этих значений, разделенных запятой. Кроме того, используется метод обработки массива Prototype detect() для поиска первого совпадающего расширения из нескольких. Версия расширения в нижнем регистре сравнивается с именем файла в нижнем регистре.

Если вам что-то непонятно, обратитесь к документации по Prototype и JavaScript. Пока же можно просто поверить, что этот способ работает в большинстве случаев. Он может не работать в старых или нестандартных браузерах и, разумеется, не будет работать, если пользователь отключил JavaScript. Такова жизнь. Мы просто ищем то, что будет работать у большинства посетителей сайта.

Если определение типа не дало результата, мы идем далее. Хотя пользователю будет показано соответствующее сообщение, можно сделать кое-что еще. Например, можно сделать определенные допущения и просто предположить тип image/jpeg, если определить тип точно не удалось. Можете доработать это решение самостоятельно. Если функция возвращает отрицательный ответ, выгрузка прерывается.

Обратите внимание, что в случае успешной выгрузки происходит перенаправление на URL, содержащий имя пользователя. Чтобы понять, зачем это нужно, обратитесь к разделу Выгрузка в S3.

Итоговый скрипт содержит в себе Perl, JavaScript и HTML, объединенные в удивительный букет (представьте себе спортивную машину с парусами, корабельным рулем и саксофоном вместо гудка). Примеры, написанные для быстрой реализации и демонстрирующие отдельные приемы, не следует рассматривать как эталон стиля и архитектуры. Я очень надеюсь, что вы не будете просто копировать приведенный скрипт. По крайней мере следует подумать о том, чтобы разделить его на отдельные элементы и переработать их. Я обещаю, что далее в этой серии статей покажу вам, как это будет сделано в составе готового web-сайта на основе mod_perl.


Заключение

Теперь вы знаете, как настроить HTML-форму для выгрузки файлов непосредственно в S3. Были использованы Perl, JavaScript и HTML. Выгрузка реализована при помощи общего скрипта, использующего библиотеку Prototype JavaScript, модули Perl MIME::Base64 и Digest::HMAC_SHA1, а также встроенный код JavaScript и HTML, сопровождающийся некоторыми пояснениями. Скрипт выполняет выгрузку в S3 одного файла одного пользователя, после чего в случае успеха перенаправляет пользователя на полученный URL на сайте share.lifelogs.com.

В части 3 мы увидим, как использовать полученный URL, и создадим запись SimpleDB для загруженного файла. Мы также изучим, как создавать, редактировать и удалять комментарии для фотографий заданного пользователя при помощи записей SimpleDB. В частях 4 и 5 мы соберем все вместе в web-сайт на основе mod_perl. Оставайтесь с нами.


Загрузка

ОписаниеИмяРазмер
Пример скрипта для этой статьиs3form.zip2KB

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=418161
ArticleTitle=Искусственный жемчуг: Часть 2. Perl и «облачные вычисления» Amazon
publish-date=08042009