Perl 開発者には CPAN (Comprehensive Perl Archive Network) という素晴らしいリソースがあります。また、Amazon にも S3 (Simple Storage Service) という素晴らしいリソースがあります。CPAN には Amazon S3 用の正式な Perl ライブラリー (「S3」と呼ばれます) がありますが、それ以外にも S3 と通信できるモジュールが少なくとも 5 つか 6 つあります (ただしその一部は完全にスタンドアロンではありません)。これはあたかも、メキシコ料理のコンテストで 200 人の参加者を予定していたら 2000 人も集まってしまった時のようです。
さて、私はこの料理コンテストの後で、コンテストの栄誉に輝いたレシピ (Perl の MOP クラスと S3 のバケットを利用したもの) を皆さんと共有しようとしています。これらのモジュールやツールは CPAN 上にあり、無料で入手することができ、即座に使い始めることができます。私が推薦する 3 つのレシピは、Net::Amazon::S3 と Amazon::S3、そして SOAP::Amazon::S3 という 3 つのモジュールに関係しています。
S3 の長所と短所についての説明は、developerWorks の連載記事、「洗練されたPerl: Perl と Amazon クラウド」を参照してください。ここでその説明を繰り返す必要はありませんが、簡単に要約しておくことにします。
S3 は、Amazon が提供、管理するストレージ・サービスです。Amazon はアクセスに対してユーザーに課金するため、ユーザーはサーバーや、バックアップ、地理的条件による利用可能な帯域幅、そして分散などについて心配する必要がありません。つまり S3 では、生活の中でのほとんどのものと同様で、便利さに対して料金を支払います。皆さんのビジネスや個人的な使用目的にとって S3 が適切かどうか判断できるのは、皆さん自身です。
S3 のデータは、(大まかにドメイン名に関連付けられる) バケットと、これらのバケットの中にあるデータ項目で構成されています。補足ですが、アップロードする際には MIME タイプを含めることが非常に重要です。さもないとデータを取得する際にそのままのバイナリー・データが返されてしまいます (これは画像の場合には特に不愉快なものです)。MIME タイプやその他のメタデータは、一度書き込まれると変更することができず、そのデータ項目を削除して再度生成する必要があります。
詳細な説明に入る前に、この記事が中級レベルの Perl プログラマーを対象に書かれていることに注意してください。この記事では Perl に関する基本的な手法を詳細に説明するわけではありません。また、Amazon の S3 とは何か、そして CPAN モジュールのインストール方法についても知っている必要があります。そうでない場合には、背景となる知識を得るための資料を挙げた「参考文献」セクションを参照してください。
この記事では、これらの 3 つのモジュール (Net::Amazon::S3、Amazon::S3、SOAP::Amazon::S3) を使用して、S3 の一連の基本的な操作を実行する方法と、必要なソース・コードについて説明します。基本的な操作として、以下を取り上げます。
- バケットの一覧表示、作成、削除
- バケットの中の項目の一覧表示、作成、取得、削除
- 項目のメタデータの取得
ここでは各モジュールによるタスクを同程度にするために、同じコマンドライン・オプションと一般的な構造を再利用しています。
Net::Amazon::S3 モジュールは、よく知られていて十分信頼されている包括的なモジュールです。Net::Amazon::S3 モジュールにはいくつかの前提条件がありますが、(Moose をよく知っている読者のために言うなら) Moose の場合のように途方もない前提条件はありません。
ここでは、S3 のキーを S3KEY および S3SECRET として環境に保存します。こうすることで、これらのキーに対して、Perl から $ENV{S3KEY} および $ENV{S3SECRET} としてアクセスすることができます。こうしたキーを利用してファイルにデータを保存する方法は、秘密の値を保護するための極めて安全な方法です。(もし S3 のキーを盗まれてしまうと、他の誰かが利用した帯域幅に対して支払う羽目になることを忘れないでください。)
さて、Net::Amazon::S3 の準備ができました。(実際には、Net::Amazon::S3 は「レガシー・インターフェース」と記述されているため、推奨されている Net::Amazon::S3::Client インターフェースが望ましかったのですが、Net::Amazon::S3::Client のバージョン 0.50 には Moose モジュールに起因するバグがあったため、この記事の執筆時点では使うことができませんでした。この問題は 0.51 で修正されており、最新のバージョンは 0.52 です。) 何はさておき、サンプルに進みましょう。いつものとおり、すべてのスクリプトは「ダウンロード」セクションから入手することができます。
リスト 1. net-amazon-s3.pl の最初の部分
#!/usr/bin/perl use warnings; use strict; use Data::Dumper; use Getopt::Long; use Net::Amazon::S3; use MIME::Types; my $mime = MIME::Types->new(); |
これは (デバッグ用の Data::Dumper を含めて) 標準的なものです。汎用の MIME オブジェクトを作成していますが、このオブジェクトはアップロードのためにしか使いません。
リスト 2. net-amazon-s3.pl のオプションとヘルパー、そして S3 への最初の接続
my %opts = (
key => $ENV{S3KEY},
secret => $ENV{S3SECRET},
separator => '/',
);
GetOptions(
\%opts,
"create|c=s",
"delete|d=s",
"list|l:s", # the parameter is optional
"keys|k",
"metadata|m",
"get=s",
"put=s",
"separator=s",
"help|h",
);
unless ($opts{key} && $opts{secret} )
{
die "$0 requires the S3KEY and S3SECRET environment variables to be set.";
}
# handle -h
usage() if exists $opts{help};
my $s3 = Net::Amazon::S3->new(
aws_access_key_id => $opts{key},
aws_secret_access_key => $opts{secret},
retry => 1,
);
die "Could not connect to S3" unless defined $s3;
sub read_filename
{
print "\nEnter filename: ";
my $name = <>;
chomp $name;
return $name;
}
sub usage
{
print lt;<EOHIPPUS;
$0 [OPTIONS]
Pass your S3 key and secret in the S3KEY and S3SECRET environment entries.
Options:
--help or -h : this help
--separator $opts{separator} : BUCKET and KEY separator character
(for --get and --put)
--create BUCKET (or -c BUCKET) : create BUCKET
--delete BUCKET (or -d BUCKET) : delete BUCKET
--delete BUCKET$opts{separator}KEY : delete KEY in BUCKET
--list [BUCKET] (or -l) : list a specific bucket or all buckets
--keys (or -k) : list the keys in each bucket (requires --list)
--metadata (or -m) : show the keys' metadata
(requires --keys and --list)
--get BUCKET$opts{separator}KEY : download KEY from BUCKET
--put BUCKET$opts{separator}KEY : upload a file to KEY in BUCKET
EOHIPPUS
exit 0;
}
|
これも標準的なものです。ここでさまざまなオプションを設定しています。usage() は非常に包括的な関数であり、この関数を使うことでプログラムが使いやすくなります。usage() ではすべてのオプションを一覧表示し、それぞれのオプションを簡単に説明しています。
リスト 3. net-amazon-s3.pl での作成操作と削除操作
if (exists $opts{create})
{
my $bucket = $s3->add_bucket( { bucket => $opts{create}} )
or die sprintf ("%s: %s", $s3->err, $s3->errstr);
print "Created bucket '$opts{create}' successfully.\n";
}
elsif (exists $opts{delete})
{
my ($b, $key) = split $opts{separator}, $opts{delete};
my $bucket = $s3->bucket($b);
die "Could not retrieve bucket $b" unless $bucket;
if (defined $key)
{
$bucket->delete_key($key)
or die sprintf ("%s: %s", $s3->err, $s3->errstr);
print "Deleted key '$key' in bucket '$b' successfully.\n";
}
else
{
$bucket->delete_bucket()
or die sprintf ("%s: %s", $s3->err, $s3->errstr);
print "Deleted bucket '$b' successfully.\n";
}
}
|
--create オプションは非常に単純です。バケットを作成しただけで戻ります。
--delete オプションは、バケットの削除 (再帰的ではないため、ユーザーは最初にバケット内のすべてのキーを削除する必要があります)、またはバケット内でのキーの削除を行います。このオプションには、バケット名とキー名とを区切るための区切り文字が登場します。
リスト 4. net-amazon-s3.pl での get 操作と put 操作
...
elsif (exists $opts{get})
{
my ($b, $key) = split $opts{separator}, $opts{get};
my $bucket = $s3->bucket($b);
die "Could not get the bucket $b" unless $bucket;
my $where = read_filename();
my $response = $bucket->get_key_filename( $key, 'GET', $where )
or die sprintf ("%s: %s", $s3->err, $s3->errstr);
die "Could not create file $where" unless -f $where;
print "Successfully downloaded $key from bucket $b into $where\n";
}
elsif (exists $opts{put})
{
my ($b, $key) = split $opts{separator}, $opts{put};
my $bucket = $s3->bucket($b);
die "Could not get the bucket $b" unless $bucket;
my $where = read_filename();
die "File $where does not exist or is not readable" unless -f $where && -r $where;
my $response = $bucket->add_key_filename(
$key,
$where,
{ content_type => $mime->mimeTypeOf($where), },
)
or die sprintf ("%s: %s", $s3->err, $s3->errstr);
print "Successfully uploaded $where into $key in bucket $b\n";
}
|
get と put は非常によく似ており、どちらもファイル名とキー名に対して動作を行います。ここで唯一興味深いものは、コンテンツ・タイプを取得するために使用する MIME::Types $mime オブジェクトです。このオブジェクトを一度アップロードすると変更できないことを忘れないでください。
リスト 5. net-amazon-s3.pl でのリスト操作
...
elsif (exists $opts{list})
{
print "Available buckets:\n";
my @todo;
if ($opts{list})
{
push @todo, map { $s3->bucket($_) } $opts{list};
}
else
{
print "(Getting all buckets)\n";
my $response = $s3->buckets;
die "Could not get the bucket list" unless $response;
@todo = @{$response->{buckets}};
}
foreach my $bucket ( @todo )
{
printf "\t%s\n", $bucket->bucket;
if (exists $opts{keys})
{
my $response = $bucket->list_all
or die sprintf ("%s: %s", $s3->err, $s3->errstr);
foreach my $key (@{$response->{keys}})
{
printf "\t\t%10s\t%s\n", $key->{size}, $key->{key};
if (exists $opts{metadata})
{
my $detail = $bucket->get_key($key->{key});
foreach my $entry (qw/content_length content_type etag/)
{
printf "\t\t\t%20s=%s\n", $entry, ($detail->{$entry}||'UNDEFINED');
}
}
}
}
}
}
|
最後の、そして最も長い --list ハンドラーは、1 つまたは複数のバケットを処理します。--list ハンドラーは、キーとキーのメタデータの一覧を、適切なスイッチと共に表示します。
\t 文字はタブに変換されます。タブはテキスト端末で適切に表示されるように出力をインデントするための簡便な手段です。
これを見るとわかるように、Net::Amazon::S3 は非常にすっきりとしたモジュールです。いくつかのメソッドは少し奇妙ですが、それらのメソッドも適切に動作します。項目の取得と、その項目を (get_key() を呼び出して) メタデータと合わせて使用することは、区別されています。このコードのどの部分も、単純で簡潔です。
Amazon::S3 モジュールは Net::Amazon::S3 をそのまま置き換えることができます。ここではハッシュ参照を使うように new() 呼び出しを変更する必要がありましたが、変更はそれだけでした (ただし当然ですが、その他にも Net::Amazon::S3 への参照をすべて Amazon::S3 に変更する必要があります)。
リスト 6. amazon-s3.pl で net-amazon-s3.pl から変更した部分
my $s3 = Amazon::S3->new({
aws_access_key_id => $opts{key},
aws_secret_access_key => $opts{secret},
retry => 1,
});
|
Amazon::S3 は多くのオプションをサポートしており、それらのオプションを使う場合にはドキュメントを参照する必要があります。一般的に言って、Amazon::S3 の目的は Net::Amazon::S3 とは少し異なるようですが、開発者から見ると API は同じです。これはライブラリーの相互運用性にとって非常に良いことです。
Amazon::S3 の好ましい特徴として、Amazon::S3 は Net::Amazon::S3 ほど多くのモジュールに依存していません。Amazon::S3 の作成者は Amazon::S3 の移植性が高いことを強調していますが、それは Amazon::S3 を必要とする人にとってさらなるメリットになります。
SOAP::Amazon::S3 モジュールには、バックグラウンドとなる知識が少し必要になりますが、この記事ではそのごく一部を簡単に説明します。SOAP は、パスを使ってリソースを示す通常の HTTP リクエストとは異なる方法で HTTP (Web) によって情報にアクセスするための手段です。通常の HTTP リクエストは一般的に REST スタイルと呼ばれ、大量の URL のバリエーションに分かれる傾向があります。一方 SOAP の場合は数個の URL にアクセスするにすぎず、大量の XML をサーバーに渡します。
ここで SOAP に関して詳しく説明したいのですが、SOAP は非常に幅の広いトピックなので、関心のある方は本や検索エンジンで調べてみてください。断言するわけではありませんが、SOAP と REST はそれほど大きく異なるものではありません。どちらを使った場合も、素晴らしいコードを作成できる場合もあり、ひどいコードを作成してしまう場合もあります。
残念ながら、ドキュメントには SOAP::Amazon::S3 は実験的なものとして記載されているため、このモジュールが皆さん自身にとって適切なものかどうかを判断するには、皆さん自身がテストする必要があります。
変更点を説明しましょう。何よりもまず、File::Slurp モジュールが必要です。SOAP::Amazon::S3 には、Net::Amazon::S3 や Amazon::S3 にあるような、ファイルを put したり、get したりするためのコンビニエンス・メソッドがないからです。
リスト 7. soap-amazon-s3.pl の最初の部分
# set Debug to 0 if you don't want to see all the XML
my $s3 = SOAP::Amazon::S3->new( $opts{key}, $opts{secret},
{ Debug => 1, RaiseError => 1 } );
|
ここでは Debug=1 に設定しています。その理由は、おそらく読者は SOAP::Amazon::S3 によって生成されるすべての SOAP トラフィックを見たいだろうと思ったからです。Net::Amazon::S3 の場合にはエラー・チェックを行いましたが、そうしたエラー・チェックはどれも、RaiseError=1 のおかげで必要ありませんでした。しかしこの設定を本番環境で使用するのは適切でないかもしれません。
バケットの作成 (my $bucket = $s3->createbucket($opts{create});) は Net::Amazon::S3 を使ってバケットを作成する場合とは異なり、またバケットの削除 ($bucket->delete()) やオブジェクトの削除 ($bucket->object($key)->delete();) も Net::Amazon::S3 の場合とは異なります。
ファイルへの書き込みは Net::Amazon::S3 の場合よりも少し複雑でした。これは先ほど説明したとおり、SOAP::Amazon::S3 には Net::Amazon::S3 にあるようなコンビニエンス関数がないからです。
リスト 8. soap-amazon-s3.pl でファイルに書き込む
open W, '>', $where or die "Could not write to $where: $!"; print W $bucket->object($key)->getdata(); close W; |
ファイルからアップロードする場合も同じです。
リスト 9. soap-amazon-s3.pl でファイルをアップロードする
my $type = ''.$mime->mimeTypeOf($where); # force $type to be a string
# use File::Slurp::read_file in scalar context to grab the whole file's contents
my $data = read_file($where);
$bucket->putobject($key, $data, { 'Content-Type' => $type });
|
強制的に MIME タイプをストリングにする必要があり、そうしないと XML ヘルパー・モジュールがエラーを起こします。また、コンテンツ・タイプの大文字/小文字の使い方も Net::Amazon::S3 の場合とは異なります。
最後に、項目を一覧表示します。この機能に関して、SOAP::Amazon::S3 は Net::Amazon::S3 ほど強力ではありませんでしたが、それはオブジェクトのメタデータが得られなかったためです。SOAP::Amazon::S3 には便利な $object->url() メソッドがありますが、このメソッドは例えば空白を %20 に変換することはできません。そのため、皆さんは $object->url() メソッドを試し、皆さんの作業に使えるかどうかを確認する必要があります。ここではバグを発生させてしまいましたが (SOAP::Amazon::S3 が実験的なモジュールであることを思い出してください)、私は当面、本番環境では $object->url() メソッドを使わないことにします。
リスト 10. soap-amazon-s3.pl でバケットとバケットの中にあるオブジェクトを一覧表示する
...
elsif (exists $opts{list})
{
print "Available buckets:\n";
my @todo;
if ($opts{list})
{
push @todo, map { $s3->bucket($_) } $opts{list};
}
else
{
print "(Getting all buckets)\n";
@todo = $s3->listbuckets;
}
foreach my $bucket ( @todo )
{
printf "\t%s\n", $bucket->name;
if (exists $opts{keys})
{
foreach my $key ($bucket->list())
{
printf "\t\t%10s\t%-30s\t%s\n", $key->{Size}, $key->name, $key->url;
if (exists $opts{metadata})
{
foreach my $entry (qw/Size ETag LastModified/)
{
printf "\t\t\t%20s=%s\n", $entry, ($key->{$entry}||'UNDEFINED');
}
}
}
}
}
}
|
これを見るとわかるように、SOAP::Amazon::S3 はメタデータを取得しません。そのため、例えばオブジェクトのコンテンツ・タイプを見ることができません。オブジェクトに関する重要な情報を、メタデータを使って保存している場合には、この制約は不便です。それを除けば、バケットとバケットの中の要素を一覧表示する機能に関して SOAP::Amazon::S3 は Net::Amazon::S3 と同等です。
この記事では S3 用の他の 2 つの CPAN モジュールについては詳細に評価しませんでしたが、以下で簡単にまとめておきます。
- Net::Amazon::S3::Tools はコマンドラインで S3 にアクセスしたい場合に便利です。Net::Amazon::S3::Tools を使った方が独自にツールを作成するよりも便利なため、既製のツールキットが必要な場合には、このツールを調べてみるとよいでしょう。
- Tie::Amazon::S3 は 1 つのバケットを扱う場合に便利なモジュールです。このモジュールを使うことで、ハッシュ・エントリーの削除や変更、新しいエントリーの追加などを行うことができます。残念ながら Tie::Amazon::S3 は、バケット・レベルの操作 (バケットの作成や削除など) や、メタデータに関する操作 (特に、新しいキーに対するコンテンツ・タイプの設定) をすることはできません。そのため Tie::Amazon::S3 は純粋なデータを S3 に保存したい場合には便利ですが、画像を保存したい場合にはあまり便利ではありません。
全体として、S3 に関する作業を行う上で、Net::Amazon::S3 とAmazon::S3 が最適の選択肢です。Net::Amazon::S3 の API とAmazon::S3 の API は交換可能であるため、必要な場合には両者を容易に切り換えることができます。どちらも、バケットの操作から項目のメタデータの取得に至るまで、すべての操作をサポートしています。Net::Amazon::S3 とAmazon::S3 の API には少し奇妙なところがありますが、いったんセットアップすれば使い方は容易です。
SOAP::Amazon::S3 から目を離さないでください。SOAP::Amazon::S3 は優れた可能性を秘めています。1 つのバケットの中で純粋なデータを扱う場合には、作業を容易にしてくれる Tie::Amazon::S3 を文句なく推薦します。
皆さんが S3 を利用して成功することを祈っています。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| Sample scripts | amazon-s3-scripts.zip | 5KB | HTTP |
学ぶために
- 連載記事、「洗練されたPerl: Perl と Amazon クラウド」(developerWorks、2009年3月から 6月) では、Perl と Apache を使用して Amazon の S3 (Simple Storage Service) と SimpleDB にアクセスする単純な写真共有 Web サイトを構築する手順を、順を追って説明しています。
- 「第 1 回」では S3 と SimpleDB の長所と短所を紹介し、両者のアーキテクチャーについて説明しています。
- 「第 2 回」では、Web ページから HTML フォームを使ってファイルを S3 にアップロードし、サーバーの負荷を最小にする方法と、厳格なセキュリティー・ポリシーを維持する方法を説明しています。
- 「第 3 回」では、アップロードされるファイルの SimpleDB レコードを URL を使って作成する方法、ある写真に特定のユーザーが書き込んだコメントを SimpleDB レコードとして作成、編集、削除する方法の詳細を説明しています。
- 「第 4 回」では、
mod_perlを利用したサイト全体のコード・ベースを検証し、最上位レベルの構成方法、それぞれのハンドラーで行っている内容、外部依存関係の設定方法などについて説明しています。 - 「第 5 回」では、
mod_perlを利用したサイト全体のテンプレートを検証しています。検証する対象は、索引用のテンプレート、アップロード用の 3 つのテンプレート (汎用、S3 フォーム用、URL 追加用)、画像とコメントの閲覧用のテンプレート、そしてある画像へのコメントを再帰的に閲覧する (つまりスレッドを順に追っていく) ためのテンプレートです。
- SOAP と REST について学ぶために、developerWorks の SOA and web services ゾーンを訪れてください。
- developerWorks の Linux ゾーンには、他にも Linux 開発者のためのリソースが豊富に用意されています。また最も人気の高かった記事とチュートリアルの一覧もご覧ください。
- developerWorks に掲載されているすべての「Linux のヒント」シリーズの記事と Linux チュートリアルを参照してください。
- developerWorks の Technical events and webcasts で最新情報を入手してください。
製品や技術を入手するために
- CPAN (Comprehensive Perl Archive Network) サイトには膨大な数のモジュールとモジュールのドキュメントが用意されています。例えば、最新の Net-Amazon-S3-0.xx モジュール、Amazon-S3-0.xx モジュール、SOAP-Amazon-S3-0.xx モジュールなどがあります。その他にも、この記事では詳しく説明しませんでしたが、最新の Net-Amazon-S3-Tools-0.xx モジュールと Tie-Amazon-S3-0.xx モジュールという 2 つのモジュールもあります。
- developerWorks から直接ダウンロードできる IBM ソフトウェアの試用版を利用して、皆さんの次期 Linux 開発プロジェクトを構築してください。
議論するために
- My developerWorks community に参加してください。開発者向けのブログ、フォーラム、グループ、ウィキなどをとおして、他の developerWorks ユーザーとやり取りすることができます。
