2005年8月28日

今日は一日mod_perlとCGI::Application::Dispatchの勉強。

CGI::Application::Dispatchは、CGI::Applicationで書かれたモジュールのインスタンスCGIをいちいち書かないで、URIから実行するモジュールを判別してインスタンス生成する、という仕組みを提供してくれます。モジュールをrunモードみたいに切り替える、という感じでしょうか(ちょっとわかりにくい…)。

PODに詳細はありますが、自分ぐらいの初級レベルの人(PerlOOP暦半年、CGI::App暦3ヶ月)でもわかるように、頭の中の整理もかねてメモしておきます。その前に、CGI::ApplicationとDispatchに関しては、ハテナオヤさんがUNIX USERのPerl短期連載で紹介していた記事が激しく参考になります。Perlの話題は、雑誌媒体への露出がPHP/Javaに比べると極端にすくないような気がするので、短期といわず長期的にやってもらいたいですね。

では、自分なりのまとめメモです。

通常、CGI::Applicationを使用する場合は、CGI::Applicationを継承したモジュールと、それをインスタンス化するCGIが必要になります。

# CGI::Applicationを継承したモジュール 
package My::App;
use strict;
use warnings;
use base qw(CGI::Application);
 
sub setup {
    my $self = shift;
    $self->start_mode('index');
    $self->run_modes(
        index => 'do_index',
        edit  => 'do_edit',
    );
}
 
sub do_index { ... }
sub do_edit  { ... }
1;


#!/usr/bin/perl
# app.cgi - My::Appをインスタンス化、実行するCGI
use strict;
use warnings;
use lib qw(./lib);
use My::App;
my $app = My::App->new();
$app->run();

この場合のURIは、

http://example.com/app.cgi?rm=index
http://example.com/app.cgi?rm=edit

となります。PATH_INFO形式が利用できる場合は、

http://example.com/app.cgi/index
http://example.com/app.cgi/edit

とすることもできます。

これに、My::Searchというモジュールが追加になった場合、それをインスタンス化するCGIは次のようになります。

#!/usr/bin/perl
# search.cgi - My::Searchをインスタンス化、実行するCGI
use strict;
use warnings;
use lib qw(./lib);
use My::Search;
my $app = My::Search->new();
$app->run();

app.cgiとsearch.cgiの差はインスタンス化するモジュール名だけですね。このような単純な違いしかないCGIをモジュールの追加のたびに書くのは(たったこれだけでも)非効率的ですし、単純だからこそミスも発生しやすいです。

ここで、CGI::Application::Dispatchの出番です。Dispatchを利用した場合、モジュールのインスタンス化は、URIに含まれたモジュール名から自動的に行ってくれます。CGIをいちいち書く必要はありません。

Dispatchモジュールを使った場合のURIは、

/dispatchのcgi/モジュール名/runモード

とすることができます。上記2つのモジュールの例(My::Searchはsearch/configのrunモードをもつ)からいくと、

http://example.com/dispatch.cgi/app/index
http://example.com/dispatch.cgi/app/edit
http://example.com/dispatch.cgi/search/search
http://example.com/dispatch.cgi/search/config

となります。モジュールがどんなに増えても、必要なCGIはdispatch.cgiのみです。で、そのdispatch.cgiは以下のようになります。

#!/usr/bin/perl
use strict;
use warnings;
use CGI::Carp qw(fatalsToBrowser);
use CGI::Application::Dispatch;
use lib qw(./lib);
 
CGI::Application::Dispatch->dispatch(
    PREFIX  => 'My',   # URIに含まれるモジュール名の名前空間
    DEFAULT => 'App',  # モジュール名が指定されなかった場合に実行するモジュール名
);

これだけです。なるほど、楽チンだ。

でも、ふと疑問に思いました。実行するモジュールが増えてMy::App::Hogeなんてのができた場合どうするんだろう?そういった場合にもちゃんと対応できるよう、URIとモジュール名をマップする仕組みが提供されていました。My::App::Hogeを/dispatch.cgi/hoge/で実行されるようにするには、

CGI::Application::Dispatch->dispatch(
    PREFIX  => 'My',
    TABLE   => {
        app    => 'App',
        search => 'Search',
        hoge   => 'App::Hoge',
    },
);

とします。ハッシュのキーをモジュールのエイリアスとしてURIに含めることができます。なるほどです。この仕組みがあれば、モジュール名のマップを作るdispatch.cgi以外にCGIを書く必要はまったくなさそうです。ただ、TABLEを定義した場合は、DEFAULTでの指定が無効になるようです(ちょっとだけはまりました…)DEFAULTの値は実際のモジュール名ではなくハッシュのキーに指定した値で指定します。

Dispatchモジュールは、CGI::Applicationフレームワークでは必須のものではありませんが、このdispatchという概念や仕組みは、その他いろいろなWebフレームワークにもあるようなので、今後もっと高機能なフレームワーク(SledgeやCatalyst)に手を出すためにもちゃんと理解しておくべき仕組みのように思います。

また、CGI::Application::Dispatchには、mod_perlを使ってApacheハンドラとして動作させたり、rewriteを使って技術に依存しないCoolなURI(.cgiとかがURIに含まれない)を作りやすくできる、とかいった副産物もあるようなです。でも自分の検索ぢからではあまり参考になる資料が発見できなかったので、もう少しいろいろ試してからまたまとめてみようかなと思います。

追記(2005/09/24) : こちらでまとめました

Comments

apache の Options ディレクティブで、
MultiViews を許可しておくと、手軽に .cgi を省いてアクセスできますよ。

最後の例で行くとこんな感じです。
http://example.com/dispatch/hoge

これで、ちゃんと、/hoge が PATH_INFO として渡されます。

でも、DocumentRoot に、同名で別な拡張子のファイルとかディレクトリがあったりすると混乱しますが(;'-')

MultiViews を使うと、静的な HTML ファイルとかでも拡張しいらずでアクセスできます。

おおぉ。MultiViewsでそんなことができるなんて全然理解してなかったです。全部Rewriteで試してました…。ありがとうございます。

「同名で別な拡張子のファイル・ディレクトリで混乱」ていうのも一度試して混乱してみたいと思います(Apacheの設定もちゃんと理解しておかないと)。

すいません、s/DocumentRoot に、/同じディレクトリに/ ですね。

ウチの環境だと、ディレクトリ>.html>.cgi って優先順位なんですが、この優先順位がどこで決定してるのかが調査不足で、分かってないんですよね。
まー、作っててもパッと分かる方がいいので、僕は、同名別拡張子のファイルを同じディレクトリには置かないようにしてます。

Apacheのコンテントネゴシエーションのマニュアルを読んで見ましたが、type-mapで優先順位を決定しているみたいです。
http://httpd.apache.org/docs/2.0/ja/content-negotiation.html

 http://exsample.com/foo
というURIにアクセスした場合、
 foo > foo/ > foo.*
の順に検索するみたいです。で、最後のfoo.*で複数見つかった場合はtype-mapで優先順位が決まっていればそのオーダーをとって、ないものはリクエストヘッダなどいろいろ調べてApacheが最適なものを探す、というふうになっているようです。むぅ…。

やっぱりわかりにくいので、MultiViewsを使って拡張子なしのURIを指定する場合は、同名別拡張子のファイルを同じディレクトリに置かないほうが懸命のようですね。

トラックバック

トラックバックURL: