レベル: 中級 Teodor Zlatanov, Programmer, Gold Software Systems
2009年 4月 08日 この 5 回からなる連載では、Amazon の S3 (Simple Storage Service) と SimpleDB を利用した単純な写真共有 Web サイトを、Perl と Apache を使用して構築します。この第 2 回目では、HTML フォームを使って Web ページから S3 へファイルをアップロードする方法を説明します。これにより、サーバーの負荷を最小限にとどめることができ、また強固なセキュリティー・ポリシーを維持することができます。
Web ページから Amazon の S3 (Simple Storage Service) にファイルをアップロードする方法は以下のようにいくつかあります。
- コマンドラインから CPAN の適当なモジュールを使って行う方法
- コマンドラインから Amazon の適当なモジュールを使って行う方法
- HTML フォームから直接行う方法
 |
この連載を最大限に活用するために
この連載を読むためには、HTTP と HTML に関する初心者レベルの知識と、JavaScript と Perl (Apache の mod_perl プロセスの内部で使われます) に関する中級レベルの知識が必要です。また、リレーショナル・データベースやディスク・ストレージ、ネットワークなどの知識があると役に立ちます。この連載は回を追うごとに技術的なレベルが高くなるため、そうしたトピックの知識が必要な場合には「参考文献」セクションを参照してください。
|
|
この記事では HTML フォームから直接ファイルをアップロードする方法を説明します。この方法を使うとサーバーの負荷を最小限にとどめることができます。アップロードに成功すると、ファイルはカスタムの URL に置かれます。この連載の今後の記事では、この URL を使って現在構築中の写真共有サイトの残りの部分をセットアップします。この連載ではドメイン名として share.lifelogs.com を使います。
S3 にアップロードする
フォームを POST することで S3 にデータをアップロードすることができます。(HTTP の PUT メソッドや SOAP の PutObject 呼び出しを使うこともできます。) この記事ではフォームを POST する方法を使いますが、その理由は、この方法が単純であり、サーバーのリソース (ディスク、CPU、帯域幅など) を使用しないためです。
S3 にアップロードする際の大きな問題はメタデータを変更できないことです。これは S3 が分散型であることによるものか、あるいは Amazon がサービスを単純なものにとどめようとしていることによるものかもしれません。S3 のフォーラムでは、Amazon はこの点を変更する可能性があることを示唆しています。
いずれにせよ、メタデータを変更できないということはコンテンツをアップロードする際に Content-Type を設定する必要があるということです。そうしないと、binary/octet-stream という、あまり望ましくないコンテンツ・タイプが設定されてしまいます。その他のメタデータはそれほど重要ではありません。なぜなら Amazon の SimpleDB を使ってアップロードが追跡され、SimpleDB にメタデータを保存することができるからです。ここでは Content-Type の問題に対して大抵は満足できる、JavaScript によるソリューションを提案します。
アップロードに成功するとユーザー名が URL の一部になります。アップロードされるメタデータにもユーザー名を入れることができるので、ユーザー名とファイルとを永久に関連付けることができます。しかしこの場合、ユーザー名を変更するには、ユーザーは何らかの方法で S3 へのアップロードをすべてやり直し、新しいメタデータを保存しなければなりません。一方で、ユーザー名には依存しないユーザー ID を使用し、そのユーザー ID を S3 のオブジェクトと関連付けることもできますが、その場合、簡単な写真共有サイトを構築するという目的に反して必要以上に複雑になります。
この時点で、私達 (つまり Web サイトのオペレーター) は images.share.lifelogs.com というバケットを S3 にセットアップしたことになります。このバケットでは、誰もが読み取れるようにするための適切なアクセス制御が行われています。もし Amazon のドキュメントを読んでもバケットのセットアップをうまく行えない場合には、S3Fox やJungleDisk などのツール、あるいはその他数多くある S3 インターフェースを利用すればバケットをセットアップすることができます。ここまでの作業を終えると、AWS へのアクセス・キーと秘密鍵も得られたことになります。
アップロードの制御にはポリシーを使用します。ポリシーというのは、JSON データ・フォーマットで表現された一連のルールです。ポリシーには Amazon の秘密鍵を使って署名する必要があります。
ポリシーに署名するためには Digest::HMAC_SHA1 という Perl モジュールを使います。まずポリシーを Base64 に変換し、すべての改行を削除し、その結果できあがったデータに署名し、その署名を Base64 でエンコードします。そして、3 回まわってつま先に触り、髪の毛を 1 本抜いて燃やし、最後に 1 セント銅価で 1.28 ドルを Elle Cowalsky 嬢に送り、あとは皆さんが使用する署名を彼女が郵便で送ってくるのを待ちます・・・。もちろんこれは冗談で、髪の毛を燃やすのはオプションです。そんなことをせずに下記を試してください。
リスト 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 = 'entire policy here';
$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 にリダイレクトされます。アップロードされる文書のサイズは 0 バイトと 1 メガバイトの間に制限されています。失効日 (expiration) の値にも注意してください (これについてはこのすぐ後に説明します)。
このポリシーの内容は署名され、誰もが見られるようになります。つまり皆さんにとって最悪の敵であっても、このポリシーの内容を偽造することは困難だとわかるはずです。このことから、ポリシーはサイトのセキュリティーの一部となります。つまりこうすることによって、特定の基準を満たした場合にのみ S3 へのアップロードが許可され、それ以外の場合には決して許可されないことになります。S3 では、あくまでも使用したサービスに対して料金を支払うことを思い出してください。それを考えると、この点は重要です。
失効日は 3000 に設定されています (そうです、西暦 3000年です)。実用的な目的から、このポリシーは失効しないことがポイントです。一方で、失効日を今から 10 分後に設定することもできます。そうすれば、例えば削除されたユーザーが、アクセス許可期間内に最後にアクセスしてから 10 分以上経過してからそのポリシーを使おうとしても、使うことはできません。しかしそうすると、ファイルのアップロードが 10 分以内に完了せずに拒否されてしまうユーザーから不満が出るかもしれません。そのため、適当に失効日を設定するのではなく、どのような期間に設定することが適切なのかを考える必要があります。
フォームの中に規定されたすべてのフィールドに対して、ポリシーでは条件を設定する必要があります。そうすることによって偽造を防ぐことができ、またポリシー文書を完全なものにすることができます。
これでポリシーを確立できたので、アップロード用のフォームをセットアップしましょう。
S3 のアップロード・フォーム
先ほど、S3 オブジェクトに関連付けられる Content-Type メタデータについて説明し、また S3 オブジェクトをアップロードする前に Content-Type メタデータを設定しなければならないことを説明しました。残念ながら画像の場合にはそうはいきません。ユーザーがどんな種類の画像をアップロードするのか、事前にわからないからです。例えば JPEG 画像と PNG 画像はコンテンツ・タイプが異なります (これらのコンテンツ・タイプは実際には MIME タイプと呼ばれ、一般的な MIME タイプは何百もあります)。
そのためのソリューションとして、中級レベルの JavaScript を使います。ここではすべてのものを 1 つの 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'; # this is the user name for the upload
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'); // note the escapes we do from Perl
var file = form['file'];
var ct = form['Content-Type'];
var filename = ''+\$F(file); // note the escapes we do from Perl
var f = filename.toLowerCase(); // always compare against the lowercase version
if (!navigator['mimeTypes'])
{
alert("Sorry, your browser can't tell us what type of file you're uploading.");
return false;
}
var type = \$A(navigator.mimeTypes).detect(function(m)
{
// does any of the suffixes match?
// note the escapes we do from Perl
return m.type.length > 3 && m.type.match('/') &&
\$A(m.suffixes.split(',')).detect(function(suffix)
{
return f.match('\\.' + suffix.toLowerCase() + '\$');
// note the escapes we do from Perl
});
});
if (type && type['type'])
{
ct.value = type.type;
return true;
}
alert("Sorry, we don't know the type for file " + 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 で申し訳ありませんが、これでも最近のほとんどのブラウザーで問題なく動作するはずです。このスクリプトは基本的に、submit ボタンをインターセプトし、アップロードされたファイルのタイプがわかる場合にのみ真を返します。
編集者からの注記: このリスト 3 のスクリプトは、ダウンロードすることができます。リスト 3 で太字になっている行は実際には改行してはいけない 2 行ですが、記事の表示幅の制限からこのようにしています。この記事からスクリプトをコピーしてペーストする際には太字で強調した各行を 1 行に戻す必要があります。ダウンロードで入手できるスクリプトでは改行が適切に行われています。
アップロード・フォーム: JavaScript と動作説明
Google API サイトから Prototype をロードします (「参考文献」を参照)。皆さんが、非常にこだわりが強かったり、何でも自分でやらなければならない強迫観念に取り憑かれていたり、あるいはすべてを自分でコントロールしたかったりするのであれば、Prototype を使わずに皆さん自身が提供することもできます (皆さんのこだわりが非常に強いからといって、Prototype の作成者のこだわりが強くないということではありません)。
Prototype ユーティリティーを使ってフォームからファイル名を取得し、(小文字の) ファイル名を調べます。ブラウザーにとって既知の MIME タイプそれぞれに対して Prototype の detect() 配列メソッドを使用し、下記の条件に最初に一致するものを探します。
- MIME タイプが 4 文字以上の長さである。
- MIME タイプに
/ 記号が含まれる。
- MIME タイプの接尾辞のいずれかがファイル名の接尾辞と一致する。
4 文字以上の長さかどうか、また / 記号が含まれるかどうかをチェックしている理由は、少なくとも Firefox には「*」 という MIME タイプがあり、この MIME タイプではすべての MIME タイプが一致してしまうからです。これでは使い物にならないため、「*」MIME タイプをスキップでき、また (可能であれば) ここでの目的にはそぐわない他の MIME タイプもスキップできる必要があります。
JavaScript の split() ストリング・メソッドを使って接尾辞に対して繰り返し処理をすると、MIME タイプを構成する文字列からなる配列が得られます。つまり例えば接尾辞が jpg,jpeg の場合には、カンマで区切られた 2 つに対して繰り返し処理をします。この場合も Prototype の detect() 配列メソッドを使用して、こうした接尾辞の中から最初に一致するファイル名を見つけます。それには、小文字で表現された接尾辞と小文字で表現されたファイル名とを比較するようにします。
混乱してわからなくなった場合には Prototype と JavaScript 一般に関する資料をよく読んでください。ここではさしあたり、一般的なほとんどの場合にはこれで十分なものとします。古かったり一般的ではなかったりする Web ブラウザーではうまく動作しない可能性があり、そしてもちろん、ユーザーが JavaScript を無効にすると動作しません。それは仕方がありません。ここではとにかく、ほとんどの訪問者に対して適切に動作するものを求めているにすぎないのです。
MIME タイプの検出に失敗すると、この方法も失敗します。その場合、ユーザーに対してメッセージが表示されますが、この部分にも改善の余地があります。例えば少し推測を行い、MIME タイプがわからない場合には構わず image/jpeg にしてしまう方法もあります。改善方法は皆さん次第です。この関数は MIME タイプの検出に失敗すると false を返しますが、それによってアップロードが中止されます。
アップロードに成功するとユーザー名を含む URL にリダイレクトされることに注目してください。なぜそうなるかは「S3 にアップロードする」セクションを参照してください。
最後に、このスクリプトには Perl、JavaScript、HTML が混在して奇妙な姿になっています (帆と梯子の付いたスポーツ・カーがサックスを演奏している様子を想像してみてください)。ある特定の手法を示すために大急ぎで作成した例をスタイルやアーキテクチャーの指針にしてはいけません。この記事に含まれたスクリプトを皆さんがコピー・アンド・ペーストする際には、少なくともテンプレート部分に分解してリファクタリングできないかどうかを検討するように望みます。この連載の今後の記事で、これが mod_perl Web サイト全体のコンテキストで機能する様子をお見せする予定です。
まとめ
今回は HTML によるアップロード・フォームをセットアップして直接 S3 にファイルをアップロードする方法を説明しました。そのために Perl、JavaScript、HTML を使用しました。Prototype の JavaScript ライブラリー、Perl の MIME::Base64 モジュールと Digest::HMAC_SHA1 モジュール、そしてインライン JavaScript と HTML を使用する 1 つのスクリプトを、いくつかの注意事項と共に説明しました。このスクリプトによって指定された 1 人のユーザーの 1 つのファイルを S3 にアップロードすることができ、アップロードが成功すると最終的には share.lifelogs.com サイトにある特定の URL にリダイレクトされます。
第 3 回では、アップロードが成功した場合の URL によって、アップロードされたファイルに対して SimpleDB レコードが作成されることを説明します。また、特定のユーザーの、ある写真の SimpleDB レコードとしてコメントを作成、編集、削除する方法についても説明します。第 4 回と第 5 回では、それまでに説明したすべてのものを mod_perl Web サイトの中に統合します。お楽しみに。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Sample script for this article | s3form.zip | 2KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | 
|  | Teodor Zlatanov は 1999年にボストン大学 (Boston University) でコンピューター工学の修士号を取得しています。彼は 1992年からプログラマーとして働いており、Perl、Java、C、C++ を使ってきています。彼が関心を持っている領域は、オープンソースによるテキスト構文解析、データベース・アーキテクチャー、ユーザー・インターフェース、UNIX システム管理などです。 |
記事の評価
|