今年はHokkaido.pmからはるばる YAPC::Asia に出張してきたkeroyonnです。
嫁にYAPCの写真を見せて「ほらみんなMacでしょー」と言ったら、不憫に思ったのかMacBookProを買ってくれました。
ひとりだけ違うメーカーのランドセルをしょった可哀想な子供を連想したらしいです。
すばらしいですなっ! YAPC は!
さて今回ご紹介する、WebService::Async というモジュールは、yusukebe さんの WebService::Simple の非同期版です。
WebService::Simple はカジュアルにウェブサービスにアクセスできる素晴しいモジュールだなぁと思っていたのですが、非同期処理ができないため同時に複数のリクエストを投げるのがちょっと面倒でした。
これを簡単に書けたらいいなーと思って作ってみました。
(これのどこが Hacker ぽいのかって? いや、ほら、あの、非同期なところとか?えーとあとは、ほら、非同期なところとか…)
特徴はこんな感じです。
参考資料はここら辺にあります。CPAN(実例が11個出てます)、YAPC::Asiaスライド(46、47枚目)、YAPC::Asia のビデオPart1、Part2(Part2の「05:05」あたりからです。時間がなくてほとんど説明してませんけど)
色々な使い方があるのですが、1アクセスごとの完了コールバックを使う場合はこんな感じです。
ここでは、Google の翻訳APIを利用しています。
use WebService::Async; use WebService::Async::Parser::JSON; my $wa = WebService::Async->new( base_url => 'http://ajax.googleapis.com/ajax/services/language/translate', param => { v => '1.0', langpair => 'en|it' }, response_parser => WebService::Async::Parser::JSON->new, on_done => sub { my ( $service, $id, $res, $req ) = @_; print $req->param->{'q'} . " => "; print "$res->{responseData}->{translatedText}\n"; }, ); $wa->add_get( q => 'apple' ); $wa->add_get( q => 'orange' ); $wa->add_get( q => 'banana', langpair => 'en|fr' ); $wa->send_request; # sending three requests in parallel.
orange => arancione ※英語→イタリア語 banana => la banane ※英語→フランス語 apple => mela ※英語→イタリア語
という感じで、デフォルトでは AnyEvent とかを意識せずに書くことができます。
100個とか同時に投げたりした場合には、ドメインごとに4並列とかでキューを捌いてくれます。
※ new 時に、auto_block => 0 を指定することで send_request 時にブロックしないようにすることもできます。
※ on_done に加えて、on_complete を使うと、全てのアクセスが完了した場合のコールバックを書くことができます。
※ on_done の引数の $id は、個々のリクエストを識別するためのキーとなっています。id は自分で指定することも自動で生成されたものを使うことも、使わずにクエリのパラメータをキー代わりにしのぐこともできます。
※ その他色々な書き方はPODを参照ください
以下のように、Cache::Memcached、Log::Dispatch を利用してキャッシングとログ出力をすることができます。
use Cache::Memcached; use WebService::Async; use WebService::Async::Parser::JSON; use WebService::Async::ResponseCache; use FindBin qw($Bin); use Log::Dispatch::Config; use Log::Dispatch::Configurator::YAML; my $configure = Log::Dispatch::Configurator::YAML->new("${Bin}/log_config.yaml"); Log::Dispatch::Config->configure($configure); my $logger = Log::Dispatch::Config->instance; my $wa = WebService::Async->new( base_url => 'http://ajax.googleapis.com/ajax/services/language/translate', param => { v => '1.0', langpair => 'en|fr', }, auto_block => 1, response_parser => WebService::Async::Parser::JSON->new, logger => $logger, ); $wa->response_cache( WebService::Async::ResponseCache->new( cache => Cache::Memcached->new( { servers => ['localhost:11211'] } ) ) ); $wa->add_get( id => 1, lang => 'en', param => { q => 'apple' } ); $wa->add_get( id => 2, lang => 'en', param => { q => 'banana' } ); my $result = $wa->send_request()
[Wed Sep 29 10:23:25 2010] [info] Does not hit any caches: http://ajax.googleapis.com/ajax/services/language/translate?q=banana&v=1.0&langpair=en%7Cfr at /opt/perl/lib/site_perl/5.10.0/WebService/Async.pm line 111 [Wed Sep 29 10:23:25 2010] [info] Does not hit any caches: http://ajax.googleapis.com/ajax/services/language/translate?q=apple&v=1.0&langpair=en%7Cfr at /opt/perl/lib/site_perl/5.10.0/WebService/Async.pm line 111 [Wed Sep 29 10:23:25 2010] [debug] Cache set at: http://ajax.googleapis.com/ajax/services/language/translate?q=apple&v=1.0&langpair=en%7Cfr at /opt/perl/lib/site_perl/5.10.0/WebService/Async.pm line 111 [Wed Sep 29 10:23:27 2010] [debug] Cache set at: http://ajax.googleapis.com/ajax/services/language/translate?q=banana&v=1.0&langpair=en%7Cfr at /opt/perl/lib/site_perl/5.10.0/WebService/Async.pm line 111
[Wed Sep 29 10:12:52 2010] [info] Cache hit: http://ajax.googleapis.com/ajax/services/language/translate?q=banana&v=1.0&langpair=en%7Cfr?q=banana&v=1.0&langpair=en%7Cfr at /opt/perl/lib/site_perl/5.10.0/WebService/Async.pm line 111 [Wed Sep 29 10:12:52 2010] [info] Cache hit: http://ajax.googleapis.com/ajax/services/language/translate?q=apple&v=1.0&langpair=en%7Cfr?q=apple&v=1.0&langpair=en%7Cfr at /opt/perl/lib/site_perl/5.10.0/WebService/Async.pm line 111
Any::Moose じゃなくて、Moose を使っているとか、依存モジュールがそれなりにあるとか、Smart::Args の新しいバージョンだと警告が出るとか色々つっこみどころはあると思いますが、私は普段便利に使っているので是非!
速度とかレン鯖でも動くというようなポータビリティを求める方には適さないと思いますが、とりあえず簡単ですし、WebService::Simple の既存コードを非同期にしたい場合にもいいかもしれません。
ちなみに私は Tatsumaki の HTTPClient のかわりに良く利用しています。
つぎは、YAPC::Asia で立ち見すらできず、会場の外に人があふれだすほどの超絶的な人気ぶりを誇った kazuho さんのターンです。