IM::EngineとStardustで3分クッキング
YAPC::Asia 2009で(少なくとも個人的に)話題だったセッションから、StardustとIM::Engineを使って3分クッキングしてみる。(実際どのくらいかかるかはよくわからん。)
概要
Stardust(CPAN, github)は簡単に使えるCOMETサーバ。COMETでStardust…ああ、ネーミングセンスが良すぎる…。てのはともかく、COMETのデモはやっぱりインパクトがあって楽しい。起動すると/channel/の下にAPIが出来て、外からそいつに適当にアクセスすれば話は済むようになっている。APIにPOSTしてあげればデータが登録されて、APIからGETすることで引き出せるってわけだ。
IM::Engine(CPAN, github)はAPI Designのセッションで登場するのだけれど、HTTP::Engineのように使える(らしい)Instant Messaging処理エンジン。なんせインターフェイスがさっぱりしていて使いやすそうだ。
となると、当然やるのはIRCのデータをIM::Engineで引っ張って、Stardustでリアルタイム出力するやつ。と思ってやってみたのだけれど、オリジナルなコードはほとんどなしに書けてしまう。なんてラクチンな世の中。
方針は、IM::Engineを仕込んだスクリプトを使って、発言の度に別で立てたStardustにPOSTする。StardustはAPIとして使って、フロントエンドインターフェイスはペラのHTMLを用意する。HTMLはStardustのDEMOでついてくるcurl_commandsの出力を真似ることにしよう。
Stardustを立ち上げる
ではまず、Stardustから行こう。
Stardustをインストールしたら、起動する。
$ stardust.pl Please contact me at: http://localhost:5742/
終わり。
StardustはSquatting(CPAN, github)というフレームワークをベースに作られている。(どちらも同じ作者。)小さなフレームワークだが、個人的にはこれまであまり見かけない書き方だったので、ちょっとドキッとした。Squatting::Cookbookがあるので、これを見るとざっくり掴めるかもしれない。3分クッキングではそれで良しとしよう。(本体もさほど大きくないので、ざっと読んでもそれほど苦労は無いが、後述するがちょっと特殊な感じがする。)
StardustのAPI
StardustのAPIは、以下のようになっている:
- GET /channelでチャンネル一覧取得
- GET /channel/$fooで任意のチャンネルの情報を取得
- POST /channel/$fooで任意のチャンネルにメッセージを送信
- GET /channel/$foo/stream/$connection_idがlong-poll用のAPI
つまるところ、Stardustは基本的に、COMETで必要なサーバ側APIをざっくり提供してくれるわけだ。
ついでなので、ここでデモページを見ておこう。デモページは起動時に--demoオプションを指定しておくと見れるようになる。
% stardust.pl --demo The demo is at: http://localhost:5742/demo/ Please contact me at: http://localhost:5742/
で、http://localhost:5742/demo/を見るとcurl_commandsというリンクあるので、そこを辿る。ターミナルから指定されているcurlコマンドを投げれば、ページ内に表示されることが分かるだろう。
インターフェイスになるHTMLを作る
今回作るフロントエンドのインターフェイスは、このcurl_commandsページを参考にする(というかコピーする)。以下のようなものにしてみた。ちょっと長いが。
<html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> <meta http-equiv="Content-Script-Type" content="text/javascript" /> <meta http-equiv="Content-Style-Type" content="text/css" /> <title>ShootingStar</title> <style type="text/css"> div.message { border-width: 0px 0px 1px 0px; border-style: solid; border-color: #999999; } span.time { font-size:small; color: #999999; } span.name { color: #666699; padding: 0em 0.5em; } span.message { color: #000000; } </style> <script type="text/javascript" src="/js/fx.js"></script> <script type="text/javascript" src="/js/jquery-1.3.2.js"></script> <script type="text/javascript" src="/js/jquery.color.js"></script> <script type="text/javascript" src="/js/jquery.ev.js"></script> <script type="text/javascript"> var clientId = Math.random().toString().replace(/\./, ''); jQuery.ev.loop( '/stardust/channel/shooting_star/stream/'+clientId, { incoming_callback: function(ev){ var dt = new Date(); jQuery('#messages').prepend( '<div class="message">' + '<span class="time">' + dt.getHours() + ':' + dt.getMinutes() + '<' + '/span>' + '<span class="name">(' + ev.incomming.sender.name + ')<' + '/span>' + '<span class="message">' + ev.incomming.message + '<' + '/span>' + '<' + '/div>' ); } } ); </script> </head> <body> <h1>ShootingStar</h1> <p>#perl-casual@chat.freenode.net</p> <div id="messages"> </div> </body> </html>
肝になるjQueryの部分は短い。COMET接続部分をjQuery.ev(github)がやってくれるからだ(Stardustと作者は同じ)。あとはちょっと体裁を整えたりとかそういうことだ。$app_root/templates/index.htmlとして保存した。
Stardustのchannel名はshooting_starにした。もちろんこれは任意だが、COMETでStardustときたらShooting Starにせざるを得ない。
アプリケーションのパス構成を変える
で、ここで気づいたのだけれど、このHTMLを表示するにはまた別にhttpdを立てる必要がある(XHRがクロスドメインを越えられないので、同一ドメイン上の必要がある)。まぁ、別に良いんだけど、ちょっと面倒だなぁ…ってことでちょっと脱線して、StardustがベースにしているSquattingを使い、以下のようなパス構成に変更してみよう。
- / : インターフェイス。上記のHTMLを表示する
- /js/* : jQueryのJavaScript各種が入るパス
- /stardust/* : Stardustのサーバを立てるパス
なんでこんなことを言い始めたかというと、ちょっと調べた限りだと、Squattingだと特定のパスに別にアプリケーションを走らせるのが簡単そうだったから。まぁ要するに試してみたいってだけなんだけど。
アプリケーションの名前は(もちろん)ShootingStarとして、次のようなアプリケーションを書いた。Stardustをコピーして書き換えただけだ。(主にいらないところを消す作業になる…。)
package ShootingStar; use 5.008; use strict; use warnings; use base 'Squatting'; package ShootingStar::Controllers; use strict; use warnings; use Squatting ':controllers'; use FindBin; use Path::Class qw( dir ); our @C = ( C( Home => [ '/' ], get => sub { my ($self) = @_; $self->headers->{'Content-Type'} = 'text/html'; return dir($FindBin::Bin)->file('../templates/index.html')->slurp; }, ), ); 1;
まぁ、静的なHTMLなので、今回はViewは省略。てかコントローラもいらないんじゃないかって感じだが、気分を出すためにもコントローラは書いてみた。もちろんSquattingはMVCフレームワークなので、Viewに渡したり出来ます。
起動用のスクリプトは、stardust.plを参考に書く。以下のような感じになった。
#!/usr/bin/perl use strict; use warnings; use ShootingStar 'On::Continuity'; use Stardust; use Getopt::Long; use FindBin; use Path::Class qw( dir ); my $config = { port => 5742 }; GetOptions( $config, "port|p=n", ); ShootingStar->mount('Stardust' => '/stardust'); ShootingStar->init(); ShootingStar->continue( port => $config->{port}, docroot => dir($FindBin::Bin)->subdir('../static/'), );
/stardustにStardustをマウントしている。あとcontinueメソッドにdocrootで渡しているのは、/js/*などの静的ファイル置き場。continueメソッドは、Squatting自体ではなくてSquatting::On::Continuityに書いてあり、これが簡単なサーバを実現してくれる。
なお、$app_root/staticの中にはstardust/shareの中に入っているjsディレクトリをそのままコピーしてきた。
ちなみに、Squattingフレームワークには、アプリケーション起動用のコマンドsquatting(まんま)がついてくる。libパスを外から指定する方法はなさそうだが、スクリプトを読むとuse lib 'lib'としているので、少なくともlibの上のパスからであれば起動可能だ。
$ cd shooting-star $ squatting -p 5742 ShootingStar Please contact me at: http://localhost:5742/
今回こうしなかったのは、/stardustにStardustアプリケーションを立てるなど、ちょっとしたカスタマイズのためである。
あとは指定されたURIをブラウザで叩く。きちんと見えていればOK。
IM::Engineを使って発言をStardustにPOSTする
最後に、発言の度にStardustにPOSTするスクリプトを書こう。といっても、IM::EngineのPODに書いてあるサンプルコードをそのままコピペして、ちょっとだけ書き換えれば良い。
#!/usr/bin/perl use strict; use warnings; use IM::Engine; use AnyEvent::HTTP; use JSON::Syck; IM::Engine->new( interface => { protocol => 'IRC', credentials => { server => "chat.freenode.net", # 任意 port => 6667, channels => ["#perl-casual"], # 任意 nick => "shooting-star", }, incoming_callback => sub { my $incoming = shift; return unless $incoming->sender->name; my $message = sprintf( 'm=%s', JSON::Syck::Dump({ type => 'incoming_callback', incomming => $incoming, })); http_post( 'http://localhost:5742/stardust/channel/shooting_star', $message, sub { printf "%s\n", $message; } ); return 1; }, }, )->run;
これでIM::Engineも終わり。チャンネルのところは自分で好きなのに変えておいてください。起動しよう。
$ script/im_engine.pl
完成!
さて、何かしら発言があるのを待とう。あるいは自分で発言したりしても良い。問題なくブラウザ側でも見れたら完成。
ちなみに所要時間は、素晴らしくいろいろ脱線してコードを読んでたので、まったく3分どころじゃなかった。
今後の展望としては、まぁログを取っておいて最初の接続時にちょっと流すとかか。頑張ってください。
ここから先はおまけ
以下はおまけ。さくっと見てみた感想。
Squatting
割とコンパクトなので、Catalystのように読むのに苦労することもないと思われる(相対的な話だけど)。あまり見かけない(気がする)書き方なのだけれど、YAPCの懇親会でご本人はプロトタイプベースのオブジェクト指向が好きだと言っていて、そう思って読み返すと多少すっきりするかも。
面白いのは、特定のパスに別のSquattingアプリケーションを割り当てたり出来る点。そういうことをmod_rewriteなどに頼らず出来るのは、ライトユースには便利かもしれない。
例えば、お遊びでCGI書くこととかあるけれど(大きなフレームワーク使うほどじゃないからCGIでいいや、みたいな時だが)、CGIの代わりにSquattingを動かしておいて、その下の特定のパスには必要に応じて別にアプリケーションを差し込むとかが出来そう。
と思って、上記ではSquattingを使ってトップページを作り、適当なパスにStardustを割り当ててみた。もともとStardust::Demoは、--demoのオプション指定時に動的に/demoに差し込まれるようになっている。
Tenjin
「とても速い」テンプレートエンジンらしい。StardustやSquattingが各ページの描画に使っている。
ベンチマークを信じるならTTの5から10倍か。そしてとても対応言語が多い。テンプレート部分の書き方は割合オーソドックスなので、さほど構えずに使えそう。(しかしTTに慣れきった自分には…。まぁ、使い分けなんだろうなぁ。)
とか言いながら今回は省略した。
[perl] TTより5倍速い?テンプレートエンジン"Tenjin"を試す - ありんく tech-logなんていう記事があった。
ちなみにフレームワークとしては、Cookbookにも書いてあるけれど別にテンプレートエンジンに縛りがある訳ではない。TTでももちろんOK。
COMET -> Stardust -> Shooting Star
何の話かってことだけれど、一応。毎年定期的に発生する流星群は、「母彗星」と呼ばれる彗星が吹き出した「ダスト」によって生み出される。なので、「彗星」→「ダスト」となったら次は「流星」なんです。