レベル: 初級 伊藤 直也, ITmedia
2007年 3月 30日 本連載ではこれまで、PerlによるWebプログラミングの基礎から応用まで解説してきました。今回は、これまで学んだことを踏まえた上で、Webサービスを使ったWebアプリケーション開発にチャレンジします。
Webサービスを利用するWebアプリケーション
Webサービスを使うと、検索システムや商品データベースを自分で持つことなしに、それらの機能を搭載したアプリケーションを簡単に作ることができるのは前回紹介したとおりです。ただ、そこで紹介したスクリプトはコマンドラインから入力を受け取り、ターミナル上でそれを表示するという簡素なものでした。
そこで今回は、Webサービスを使いつつ、入力や出力をWebブラウザで行うもう少しリッチなアプリケーション、すなわちWebアプリケーションを作ってみたいと思います。その過程の中で、テンプレートエンジンやフレームワークの使い方を解説していきたいと思います。
Open Searchで検索
今回扱うWebサービスは、Amazon.comの子会社であるA9.comが提供するOpen Searchです。
Open Searchは、複数の検索エンジンから自分の好きなものを組み合わせ、まとめて検索できる検索サービスです(図1)。例えば「Google」という単語で検索したときに、Yahoo!のWeb検索、Googleのイメージ検索、Amazon.co.jpの商品検索を3つまとめて検索可能です。
図1 A9.com
また、Open Searchは検索エンジンをまとめて利用できるというだけでなく、検索エンジンの組み合わせ候補として、サードパーティーが自社の検索エンジンを登録できるという意欲的な試みとなっています。例えばわたしが所属するはてなの検索サービスであるはてな検索もOpen Searchに対応していて、A9.comから利用できます(図2)。
図2 はてな検索
Open Searchに対応するには、A9.comから指定されたフォーマットで検索結果のRSSフィードを出力し、Open Search DescriptionDocumentと呼ばれる検索エンジンの仕様を記載したXML文書をサイト上で公開します。
これを逆に見ると、「Open Searchに対応している検索エンジンは、検索結果のRSSフィードを出力していて仕様がXML文書で公開されているので、第三者がそれを取得できる」ということでもあり、かつ「OpenSearchに対応している検索エンジンは、すべて同じA9.comが指定したとおりのインタフェースで、Webサービスとして利用できる」ということでもあります。
実例を見た方が分かりやすいかもしれませんね。CPANに登録されているWWW::OpenSearchというモジュールを使って、Open Search対応の検索エンジンをWebサービスとして使ってみましょう(リスト1)。
リスト1 WWW::OpenSearchモジュールを使った検索エンジン
#!/usr/local/bin/perl
use strict;
use warnings;
use Jcode;
use WWW::OpenSearch;
my $word = shift;
my $osxml = shift || "http://search.hatena.ne.jp/osxml";
# Open Search Description Document のURL
my $engine = WWW::OpenSearch->new($osxml);
my $feed = $engine->search($word);
printf("%s\n", Jcode->new($_->{title}, 'utf8')->euc)
for @{$feed->items}; |
WWW::OpenSearchは、任意の検索エンジンのOpen Search Description DocumentのURLを渡すと、自動でその検索エンジンの仕様を解析し、検索結果をXML::RSSインスタンスとして返してくれるという便利なモジュールです。XML::RSSインスタンスを操作すれば、検索結果のタイトルや概要などが取得できる、という具合です。
このスクリプトを実行すると、デフォルトでは、はてな検索のOpen Search Description Documentが利用されます(実行例1)。ここでは「perl」という単語で検索してみました(検索結果としてはてな検索の結果が表示されています)。
実行例1 Open Search対応検索エンジンを実行したところ
$ perl opensearch.pl perl
Perl
mod_perl
BioPerl
:
: |
このスクリプトに対して、別の検索エンジンのOpen Search Description DocumentのURLを渡してみましょう。blog検索エンジンのBulkfeedsのものを指定してみます(実行例2)。
実行例2 Bulkfeedsのエンジンを用いて検索を実行
$ perl opensearch.pl perl http://bulkfeeds.net/opensearch.xml
PC・システム開発
[ARG]Windows版MRTGをサービスとして起動する
perl 多用警報
:
: |
検索結果がBulkfeedsのものに変わりました。このように、Open Search対応の検索エンジンは、それぞれOpen Search DescriptionDocumentのURLが異なるだけで、まったく同じインタフェースで扱うことができるのが特徴です。
出力をテンプレートエンジンで整形する
さて、Open Searchを使ったWebアプリケーションの開発に入っていきますが、いきなりアプリケーションを作る前に、出力をどうやってHTMLに整形するか考えてみましょう。
真っ先に思いつくのは、リスト2のようにPerlスクリプト中にHTMLを書くスタイルです。しかし、HTMLのデザインを変更するのにスクリプト本体を変更しなければいけなかったり、行数が増えてくるとスクリプトの可読性が低くなったりするので、この方法はあまりお勧めできません。
リスト2 Perlスクリプト中にHTMLを書いた場合
print "<html>";
print "<head><title>Open Search</title></head>";
printf ("<p><a href="%s">%s</a></p>\n", $_->{link}, $_->{title})
for (@{$feed->items});
:
: |
そこで利用するのがテンプレートエンジン。マクロを実行できるテンプレートとしてHTMLを用意する方法です。これも利用例を見るのが分かりやすいと思います。Perlの代表的なテンプレートエンジンであるTemplate-Toolkit*(以下、TT)を使って先のスクリプトを整形してみましょう。
まず、スクリプトをリスト3のように書き換えて、TTで出力するように変更します。このようにTemplateインスタンスのprocessメソッドに、
- テンプレートファイル
- テンプレートから利用するオブジェクトやデータ構造
を渡して実行すると、テンプレートファイルを読み取ってマクロを展開して出力してくれます。
リスト3 Template-Toolkitを使ってHTMLを出力
#!/usr/local/bin/perl
use strict;
use warnings;
use WWW::OpenSearch;
use Template;
my $word = shift;
my $osxml = shift || "http://search.hatena.ne.jp/osxml";
my $engine = WWW::OpenSearch->new($osxml);
my $feed = $engine->search($word);
Template->new->process("./search.tt", { result => $feed }); # TT で出力
|
次に、スクリプトとは別に、テンプレートファイルを用意します。例えば検索結果をul、liタグを使ってマークアップするテンプレートはリスト4のようになります。これを実行すると、実行例3のように整形されて表示されます。テンプレートエンジンを使ったことで、スクリプトと出力のHTMLをきれいに分離できました。
リスト4 検索結果をul、liタグを使ってマークアップするテンプレート
<ul>
[% FOREACH item IN result.items -%]
<li>[% item.title | html %]</li>
[% END -%]
</ul>
|
このようにTTでは「[% var %]」や「[% FOREACH var IN list %]」といったマクロを使ってテンプレートを記述します。より詳しい使い方はCPANのTTのドキュメントを参照してください。
Webアプリケーション開発にはフレームワークを使おう
出力の整え方は分かったので、ここからWebアプリケーションを作りますが、今回は見通しを良くするため、mod_perlなどは使わず、CGIプログラムとしてアプリケーションを作っていきます。
Webアプリケーションというと大層な感じがしますが、すでにWWW::OpenSearchで検索、結果を受け取るという中核の処理、それから検索結果を整形して表示するという出力部分は分かっています。残る作業は、入力を受け取ってそれに応じてアプリケーションを動かす部分の作成くらいです。
この部分、つまり入力を受け取ってうんぬんというスクリプトを、実装するとどんな具合でしょうか。よくあるのはリスト5のようなパターンです。
リスト5 「いまどき」でないWebアプリケーションプログラム
#!/usr/local/bin/perl
use strict;
use warnings;
my $q = CGI->new;
if ($q->param('mode') eq 'search')
{
&do_search($q);
}
elsif ($q->param('mode') eq 'config')
{
&do_admin($q);
}
else
{
&do_index($q);
}
sub do_search
{
my $q = shift;
:
:
}
sub do_admin
{
:
:
}
sub do_index
{
:
:
}
|
このスクリプトでは、アプリケーションのトップページ、検索結果ページ、設定ページの出力処理をすべて1つのファイルにまとめています。それぞれの動作は、URLに与えられたmodeなどのクエリパラメータの変数によって切り替え、サブルーチンを呼び出すことで実装するというものです。うーん、ちっとも「いまどき」な感じがしませんね。この方法だと、アプリケーションを拡張するとどんどんスクリプトの行数が増えていって、後からメンテナンスするのが大変になってしまいます。
そうならないためには、どうすれば良いでしょうか? こういった問題を追究すると、例えば各ページごとにモジュールやクラスを切り分けて……といった方向へ発展していくわけですが、その仕組みそのものを一から作るのは「車輪の再開発」にほかなりません。「Webアプリケーションを作るための仕組み」は、フレームワーク*としてすでにあるものを使ってしまうのが良いでしょう。これもやっぱりCPANです。
CGI::ApplicationでWebアプリケーションを作る
CGI::Applicationを使うと、先の例のような一枚っぺらでメンテナンス性の低い構成ではなく、ページ(機能)ごとにクラスを分けつつ、かつmodeでの条件分岐のような泥臭い処理を書かずにWebアプリケーションを作れるようになります。
CGI::Applicationでは、CGIスクリプトそのものには処理を書きません。CGIスクリプトからはクラスを呼び出して実行するだけです。例えば次のようになるでしょう。
#!/usr/local/bin/perl
use WebApp; # CGI::Application のサブクラス
my $webapp = WebApp->new;
$webapp->run;
|
具体的な処理が入っているWebAppクラスの実装はリスト6のようになります。CGI::Applicationを継承して、setupメソッドをオーバーライドしオブジェクト指向インタフェースでどのモードがどのメソッドを呼ぶか、というふうに記述していきます。if文で分岐処理を書くよりもずいぶんすっきりしますね。
リスト6 WebAppクラスの実装
package WebApp;
use base qw (CGI::Application); # CGI::Application を継承
# 各モードのディスパッチ先メソッドのセットアップ
sub setup
{
my $self = shift;
$self->start_mode('search');
$self->mode_param('__mode');
$self->run_modes(
search => 'do_search',
config => 'do_config',
);
}
sub do_search { …… }
sub do_config { …… }
|
CGI::Applicationは$self->mode_paramで指定した名前のURLのクエリパラメータをモードとして扱い、その値によってメソッドのディスパッチ*先を決定します。またstart_modeで指定した値がデフォルトのモードになります。つまり、前述の書き方だと、
- cgiapp.cgi?__mode=config → do_configが呼ばれる
- cgiapp.cgi → デフォルト → do_searchが呼ばれる
といった具合になります。また、各メソッドの戻り値がそのモードのページの出力です。例えば、
sub do_search
{
return "hoge";
}
|
のようにすると、「cgiapp.cgi?__mode=search」にアクセスしたときの画面には、「hoge」と1行表示されます。
CGI::Applicationでは、ページの切り替えはmodeパラメータで行うか、別のクラスを作って分けるといった具合に設計していくことができます。ある特定の機能に関連したページは1つのクラスの中にまとめて適宜modeで切り替え、異なる機能のページはクラスを分けるといった感じでページ設計をしていくと良いでしょう。
CGI::Application::Dispatchで呼び出すクラスのディスパッチ
CGI::Applicationを単体で使うのもいいですが、ページごとにクラスを用意するような設計を行う場合はCGI::Application::Dispatchを使うと便利です。例えば、リスト7がcgiapp.cgiというCGIプログラムだとすると、「cgiapp.cgi/<モジュール名>/<モード>」というURLで該当ページにアクセスできるようになります。具体的には、
- cgiapp.cgi → UU::Indexのデフォルトのモード
- cgiapp.cgi/search → UU::Searchのデフォルトのモード
- cgiapp.cgi/search/config → UU::Searchのモードconfig
- cgiapp.cgi/edit/enter → UU::Editのモードenter
といった具合でディスパッチされます。
リスト7 CGIプログラムの例
#!/usr/local/bin/perl
use strict;
use CGI::Application::Dispatch;
CGI::Application::Dispatch->dispatch(
PREFIX => 'UU',
DEFAULT => 'Index',
);
|
それではフレームワークの解説はここまでにして、次回は実際にWWW::OpenSearchを使ってアプリケーションを組み立てていきましょう。
このページで出てきた専門用語
-
テンプレートエンジンであるTemplate-Toolkit
- CPANではTemplateの名前で登録されています。
-
フレームワーク
- Perlの代表的なWebアプリケーションフレームワークとして、次のものが挙げられます。
- CGI::Application
- Mason
- Sledge
- Catalyst
この中でも特に、最近はCatalystが非常に注目されていますが、これは、「Ruby on Rails」というRubyで利用するフレームワークが流行している影響もあります(CatalystはRailsを参考に作られています)。Catalystは便利なのですが、利用方法を覚えるまでに若干敷居が高く今回は解説しませんでした。興味のある方はぜひ挑戦してください。
-
ディスパッチ
- 処理を振り分けること。会社の受付が来客の応対と対応部署への誘導を専門に行うのと同様、プログラムにおいても、入力データを受け付けて該当処理を呼び出すプログラム構造を持つことがあります。こういった処理をディスパッチと呼びます。
参考文献
著者について  | |  | 伊藤直也,ITmedia |
記事の評価
|