728 Views
February 27, 24
スライド概要
2012/09/29 に YAPC::Asia Tokyo 2012 で発表したスライドです。
I like 🚌 and ☕
モダンmod_perl入門 (Modern mod_perl Guide) 尾形 鉄次 (OGATA Tetsuji) Twitter: @xtetsuji 2012/09/29 YAPC::Asia Tokyo 2012
Attention for audience • This slide is mainly written by Japanese, and few English. In the future, I will write and share this slide of English version, perhaps. • I speach by Japanese language. • If you do not known Japanese language, please fun and feel from some Perl code and few English description on this slide.
Self-introduction
自己紹介 • 尾形 鉄次 (OGATA Tetsuji) • Twitter: @xtetsuji • Blog: http://post.tetsuji.jp/ • SlideShare: http://www.slideshare.net/xtetsuji • よく行く: Hokkaido.pm, Hachioji.pm • 得意技: mod_perl (他はよく知らない)
所属紹介 • 株式会社fonfun(フォンファン) http://www.fonfun.co.jp/ • 主力製品:リモートメール http://rmail.jp/
Agenda
Agenda • About mod_perl basics • mod_perl handler basics • Refactoring mod_rewrite • mod_perl meets PHP • Apache worker MPM and Perl ithreads • compare mod_{lang} families • In future my activities
If time is left • Compare with others • Connection cycle: Let’s make SMTP Server by mod_perl • Other topics
fi In the rst
Respect for ... モダンPerl入門 (翔泳社 2009; http://books.shoeisha.co.jp/book/b73388.html )
参考文献 • 洋書になりますが、mod_perlを勉強する上で 役立つ3冊 • Practical mod_perl • The mod_perl Developer’s Cookbook • mod_perl2 User’s Guide (Orelly 2003; http://modperlbook.org/) (Sams Publishing 2002; http://www.modperlcookbook.org/) (Onyx Neon 2007; http://modperl2book.org/)
About mod_perl basics
History of mod_perl • mod_perl1 • mod_perl2 rst public release: 1996/3/25 (see: http://perl.apache.org/dist/mod_perl-1.0-current/Changes) rst public release: 2002/4/6 fi fi (see: http://perl.apache.org/dist/mod_perl-2.0-current/Changes)
Perl CGI の高速化 • mod_perl1 • mod_perl2 # ↓Apache Con gurations AddHandler perl-script .pl PerlHandler Apache::Registry AddHandler perl-script .pl PerlResponseHandler \ ModPerl::RegistryPrefork • こう書くことで、拡張子”.pl”のPerl CGI fi が高速化する (Registry→PerlRunでも可)
Perl CGIの高速化 • CGI.pm、CGI::Simple 等は内部で mod_perl高速化環境をサポート済み • とはいえ永続環境なので、グローバル 変数の扱い等注意が必要 →モダンPerlなコードを書こう
mod_perlって何? • Perl CGIの高速化?→副次的な効果 • 本当は「Apacheがモジュールで提供す る機能をC言語を使わずPerlで書ける ようにしたもの」がmod_perl • C言語が読み書きできない俺歓喜
About mod_perl basics • Perl CGIの高速化についてはだいぶ割愛 させていたきますが、世間に出回って いる間違った知識の是正だけはどこか でしたい • 後はApacheのmod_perl拡張ハンドラを 読み書きして楽しみましょう
mod_perl handler basics
mod_perl essence is extension of Apache • mod_perl の Perl CGI高速化環境では、 HTTPリクエストを受けてレスポンスを 返すことが出来るのは周知の通り • mod_perlはHTTPレスポンスを返すだけ でなく、その前処理や後処理もできる
Apache internal • MPMは多く使われているpreforkを想定 • 親プロセスを起動すると、設定に従っ て子プロセスを設定の数だけ起動 (prefork) • 各子プロセスはHTTPリクエストを待つ • では子プロセスごとのHTTP処理は...
Apache internal • リクエストを受ける • ヘッダを解析する • 必要に応じてURLを変換したり、DocumentRootを手がかりに実パス を割り出す • アクセス制御、認証、承認(BASIC認証など) • MIMEタイプを考える • レスポンスを出す(静的ファイル、プログラム出力) • ログ(アクセスログ、エラーログ)を出力する • 次のリクエストを待つ
Apache internal • 各処理を行っている部分にはフックが あって、ここに各種処理を挟むことが 可能 → Apache Module(mod_xxxxxx) • Apache Module でできることを Perl でできるようにしたもの → mod_perl
Apache/mod_perl process phase • 各フックがある部分: 「(処理)フェーズ」 • リクエスト待ち状態から各フェーズを 一巡するので、全体を 「リクエストサイクル」と呼びます
Phase of mod_perl1 PerlChildInitHandler PerlPostReadRequestHandler PerlInitHandler PerlTransHandler PerlHeaderParserHandler PerlAccessHandler PerlAuthenHandler PerlAuthzHandler PerlTypeHandler PerlFixupHandler PerlFixupHandler PerlHandler PerlLogHandler PerlCleanupHandler PerlChildExitHandler ※一部省略があります。詳しくは以下を参照 fi http://perl.apache.org/docs/1.0/guide/con g.html#toc_Perl_Handlers
Phase of mod_perl1 ※「Practical mod_perl」より抜粋
Phase of mod_perl2 PerlChildInitHandler PerlPostReadRequestHandler PerlInitHandler PerlTransHandler PerlMapToStorageHandler PerlHeaderParserHandler PerlAccessHandler PerlAuthenHandler PerlAuthzHandler PerlTypeHandler PerlFixupHandler PerlFixupHandler PerlResponseHandler PerlLogHandler PerlCleanupHandler PerlChildExitHandler ※かなり省略があります。詳しくは以下を参照 fi fi http://perl.apache.org/docs/2.0/user/con g/con g.html
Phase of mod_perl2 ※「Practical mod_perl」より抜粋
fi
rst mod_perl1 handler
package MyApache::Hello;
use strict;
use warnings;
use Apache::Constants qw(OK);
sub handler {
my $r = shift; # "Apache" object
$r->send_http_header('text/plain');
$r->print("Hello! mod_perl1.\n");
return OK;
}
1;
fi
rst mod_perl2 handler
package MyApache2::Hello;
use strict;
use warnings;
use Apache2::RequestRec (); # for $r->content_type()
use Apache2::RequestIO (); # for $r->print()
use Apache2::Const -compile => qw(OK);
sub handler {
my $r = shift; # "Apache2::RequestRec" object
$r->content_type('text/plain');
$r->print("Hello! mod_perl2.\n");
return Apache2::Const::OK;
}
1;
Apache{1,2} con gs
# Apache1
<Location />
SetHandler perl-script
PerlHandler MyApache1::Hello
</Location>
fi
# Apache2
<Location />
SetHandler modperl
PerlResponseHandler MyApache2::Hello
</Location>
fi rst mod_perl handler • 全ては sub handler と $r の受け取りから • これはどの処理フェーズでも同様 • mod_perl2 では $r のメソッド群が各種 別パッケージで管理されているため、 メソッドによって事前に適切な Apache2::* を use しておく必要がある
fi rst mod_perl handler • PerlHandler / PerlResponseHandler にハン ドラをセットしておけば、全てのファ イルの処理をフックできるので、*.html ファイル等をあたかも透過的に処理す ることができる → 絵文字処理など
Refactoring mod_rewrite
About mod_rewrite • URLを書き換えるApacheモジュール • 「Apacheのスイス製アーミーナイフ」 • 簡単なコードでURL書き換えが可能だ けれど、数十行も書けば魔窟の完成 • 黒魔術と呼ばれる理由
Refactoring mod_rewrite • mod_perl では PerlTransHandler という URL を書き換えるフェーズを用意して いる • ここに URL 変換処理を挟める
Apache1 URI Trans
package MyApache1::Trans;
use strict;
use warnings;
use Apache::Constants qw(DECLINED);
sub handler {
my $r = shift;
my $uri = $r->uri(); # e.g. "/path/to/foo.html"
### ... modify $uri ...
$r->uri($uri);
return DECLINED; # I tell a lie that I do nothing.
}
1;
Apache2 URI Trans
package MyApache2::Trans;
use strict;
use warnings;
use Apache2::RequestRec (); # for $r->uri()
use Apache2::Const -compile => qw(DECLINED);
sub handler {
my $r = shift;
my $uri = $r->uri(); # e.g. "/path/to/foo.html"
### ... modify $uri ...
$r->uri($uri); # set $uri
return Apache2::Const::DECLINED;
# I tell a lie that I do nothing.
}
1;
Apache{1,2} con gs
# Apache1
# e.g. In <VirtualHost> Directive
PerlTransHandler MyApache1::Trans
fi
# Apache2
# e.g. In <VirtualHost> Directive
PerlTransHandler MyApache2::Trans
mod_perl URI Trans pros than mod_rewrite • mod_rewriteに比べて可読性が高くなる • DBを引いたりmemcachedにアクセスし たりPerlで出来ることは何でもできる
mod_perl URI Trans cons than mod_rewrite • 複数のファイル管理が必要となる(*.pm) • Perl/mod_perlが分からない担当者への 引き継ぎが難しくなる • Directory Context(<Location>, .htaccess) に書かれたmod_rewrite設定の場合は、 別途ケアが必要なケースもある
mod_perl meets PHP
PHP pathetic story of Perl Monger • 誰かが発注したアプリがPHP製で、既に 勝手に納品しちゃっている • 蓋を開けると実装とかがテキトウ過ぎ • 契約や諸々の理由で手を入れられない • 運用担当でPerlしか知らない俺がPHPの デバッグとか…
mod_perl meets PHP • PHPが実行される前後にmod_perlで何か フックを差し込めないか • PHP処理前に認証・許可処理 • PHP処理後に出力をフィルタ…等々
Review request phase
Access/Authen/Authz and PHP • session_*()の使い方等がダメなケースで は、PHP中のそれらのコードを除去して mod_perlのAccess/Authen/Authzフェーズ で対処するケースも考えられる • Cookie、X-UP-Subno等のRequest Header は全て読める → 認証を肩代わりできる
package MyApache2::Auth;
use strict;
use warnings;
# $r->headers_in() and $r->headers_out() returns APR::Table
use APR::Table
();
use Apache2::RequestRec ();
use Apache2::Const -compile => qw(OK REDIRECT);
sub handler {
my $r = shift;
my $cookie = $r->headers_in->get('Cookie'); # raw cookie
my $x_up_subno = $r->headers_in->get('X-UP-Subno');
my ($is_success, $location, $set_cookie);
###
### ... modify and analyze this session information ...
###
if ( $is_success ) {
$r->headers_out->set('Set-Cookie' => $set_cookie);
return Apache2::Const::OK;
}
else {
$r->headers_out->set(Location => $location);
$r->err_headers_out->set(Location => $location);
return Apache2::Const::REDIRECT;
}
}
# e.g. In <VirtualHost> Directive
1;
PerlAccessHandler MyApache2::Auth
Request output lter and PHP • PHPの出力をフィルタする • Apache2のネイティブフィルタなので、 PHPの ob_*() (output buffering) 等の設定 に一切影響されない • 応用例: 絵文字変換、PHPで書ききれな fi い部分を後で置換する、等々
Requets output lter practice • Filter の場合 sub handler { ... } は $r (Request Object)ではなく、 $f (Filter Object) を第一引数に受け取る • PerlOutputFilterHandler ディレクティブ • 今回は改行を除去する簡単なサンプル fi を紹介
package MyApache2::FilterObfuscate;
use
use
use
use
APR::Table
();
Apache2::Filter
();
Apache2::RequestRec ();
Apache2::Const -compile => qw(OK);
my $READ_CHUNK_LENGTH = 2048;
sub handler {
my $f = shift; # $f is "Apache2::Filter" object
### If filter is chained, this filter is first?
unless ($f->ctx) {
$f->r->headers_out->unset('Content-Length');
$f->ctx(1);
}
while ($f->read(my $buffer, $READ_CHUNK_LENGTH)) {
$buffer =~ s/[\r\n]//g;
$f->print($buffer);
}
return Apache2::Const::OK;
}
1;
# e.g. In <VirtualHost> Directive
<FilesMatch “.*(html?|php)$”>
PerlOutputFilterHandler MyApache2::FilterObfuscate
</FilesMatch>
PHP and Perl are friend • Apacheの上でPHPとPerlは友達! • 他にも各種フックを使ってmod_perlは PHPを助けることができる • 可能性は無限大
Apache worker MPM and Perl ithreads
Apache worker MPM and Perl ithreads • Apache worker MPM: スレッド(pthread) とプロセスのハイブリット動作 • Perl ithreds: 嫌われ者 • 同時並行処理するならParallel::Prefork 等やAnyEventを使うのが今のPerl流儀
スレッドのメリット • 各リクエストフェーズ間で変数共有が 可能 (poor man’s memcached?) • 何か人とは違う事をしてるワクワク感
スレッドのデメリット • Perl ithreadsが安定していない • 各スレッド毎にインタープリタプール を作成する設計上、preforkとコストは それほど変わらない • スレッドセーフを意識する必要がある
Instruction worker MPM & mod_perl • worker MPM上でのmod_perlのノウハウ は特に少ないのでハマると危険 • Perlのスレッドは不安定である事を心得 た上で、複雑な事はさせてはいけない
Queue server by mod_perl2 thread • 今回はスレッドの変数共有を利用した mod_perl2の文字列Queueサーバを実演 • “POST /” でRequest BodyをQueueing、 • “GET /” でQueueをPickup
Queue server by mod_perl2 thread (1) package My::Queue; use strict; use warnings; use threads; use threads::shared; my @queue :shared; sub push_queue { my $value = shift; return 0 if !defined $value; push @queue, $value; return 1; } sub shift_queue { return shift @queue } 1;
Queue server by
mod_perl2 thread (2)
package MyApache2::ThreadQueue;
use strict;
use warnings;
use threads;
use Apache2::RequestRec ();
use Apache2::RequestIO
();
use Apache2::Const -compile => qw(OK HTTP_METHOD_NOT_ALLOWED);
use My::Queue;
my $READ_CHUNK_LENGH = 2048;
###
### ... following sub handler { ... } ...
###
Queue server by
mod_perl2 thread (3)
### continued
sub handler {
my $r = shift;
if ( $r->method eq 'POST' ) {
my $value;
while ( $r->read(my $buf, $READ_CHUNK_LENGH) ) {
$value .= $buf;
}
My::Queue::push_queue($value);
$r->content_type('text/plain'); $r->print('Queued');
return Apache2::Const::OK;
}
elsif ( $r->method eq 'GET' ) {
my $retval = My::Queue::shift_queue();
$r->content_type('text/plain'); $r->print($retval);
return Apache2::Const::OK;
}
else {
return Apache2::Const::HTTP_METHOD_NOT_ALLOWED;
}
}
1;
worker MPM and perl ithreads practice • 複雑な事はさせてはいけない • 複雑な事をさせる場合には十分試験を • 大事なことなので(ry • 前出のQueue serverは100KB程度の文字 列の入出力を10K/minさせても問題無し
compare mod_{lang} families
mod_{lang} (1) • mod_perl: 最も歴史が古いものの一つ • mod_ruby: 開発停滞状態→mod_mruby • mod_python: 開発停滞状態 • mod_lua: Apache2.4からコアモジュール • mod_mruby: 2012年4月に登場し活発
mod_{lang} (2) • mod_wsgi: Python WSGI 実装 • mod_php: いわゆるPHP • こちらは今までの意味でApacheを 「拡張」するものではない • 他にもたくさんmod_{lang}はあるらしい
Benchmark http://blog.matsumoto-r.jp/?p=2669 より
Benchmark http://blog.matsumoto-r.jp/?p=2669 より
Benchmark • Perl CGI高速化環境(PerlRun, Registry)の 場合はmtime(ファイルの最終更新日時) を都度見るので、statシステムコールの コストが無視できない • mod_perlネイティブハンドラで勝負!
Benchmark • 実際、mod_perlネイティブハンドラで 同等のベンチマークを行うと About 7000 Response/secとのこと (Thanks @matsumotory) • Yet another perl5 implement で mod_*perl を作ると良い勝負ができるかな
In future my activities
In future my activities • mod_perlの役立つ情報やコードを公開 していきたい • mod_perlで困っている人を助けたい
In future my activities • 今後もmod_perl界隈を盛り上げたい • Twitterアカウント作った @mod_perl_info • ウェブサイトも作成中(未完成) http://modperl.info/ • 詳細、割愛した部分の続きはどこかで
Timeup?
Questions?
☆Service Slides☆
Compare with others
Compare with others • Pure CGI • FastCGI • PSGI/Plack, WAF
mod_perl and pure CGI • パフォーマンスを要求しなければ良い • Apache以外のウェブサーバでも使える • レンタルサーバ等、mod_perlすら使え ない環境は未だに多い
mod_perl and FastCGI • どちらも同じ永続環境、適材適所 • Apache拡張ならmod_perl、CGIの高速化 のみならFastCGI • mod_perlの場合Apacheプロセス自身が 肥大化していくことを嫌う向きもある
mod_perl and PSGI/Plack, WAF • モダンなウェブアプリを今から書くの であればPlackベースのWAFを選ぼう • Apacheを拡張して他の言語との連携や Apache自体の動作を拡張できるのは mod_perlの面目躍如
Should you choice mod_perl? • Apacheという枠の中ではあるものの、 mod_perlの可能性はとても広い • パフォーマンス、移植性、様々な要素 を検討して、mod_perlを使うか考える • 既にあるApache上のレガシーコードを リファクタリングするなら良い選択
Connection cycle: Let’s make SMTP Server by mod_perl
Connection cycle • HTTP以外のサーバを書きたい • Apacheと同様の安定性が欲しい • Apache2/mod_perl2では、Connectionを HTTP以外の自前のものに書き換える事 ができる (e.g. mod_ssl)
About Connection Cycle PerlPreConnectionHandler PerlProcessConnectionHandler ※「Practical mod_perl」より抜粋
Why I want to write SMTP server by perl • DB引きたい • 絵文字処理したい • 通常のMTA(Sendmail/qmail/Post x等)の fi pipeではスケールできない
Qpsmtpd • http://smtpd.develooper.com/ • 実際に私(会社)もmod_perl2 Connection cycleでSMTPサーバを実装してみたもの の、既にPerl界隈にはQpsmtpdという MTAがある
Qpsmtpd has some Engine=Transport • pipe (CGI like) • fork-server • prefork-server • Apache (Apache::Qpsmtpd) • async (Danga::Socket base)
Qpsmtpd’s Apache::Qpsmtpd • Apache2 / mod_perl2 の Connection cycle の良い応用例 • これを見るとApache2をEngineとして MTAを作る方法が良く分かる
Other topics
Test of mod_perl handler • 「モダンPerl入門」に詳しく書いてある • Apache2::FakeRequestを使って偽物の$r を作ってハンドラをテストできる • 処理単位のテストはTest::More等で • 詳しくは割愛、もしくは上述の書籍を
nginx: HttpPerlModule http://wiki.nginx.org/HttpPerlModule
nginx: HttpPerlModule • 構文や考え方がmod_perl1と酷似 →mod_perlの知識を流用可能 • ただしイベント駆動サーバならではの 各種制限もあるので注意 • 世間での応用例も少し出てきている • 以前はEmbeddedPerlModuleという 名前だった(1.0→1.2で名称変更?)
nginx:
HttpPerlModule
package hello;
use nginx;
sub handler {
my $r = shift;
$r->send_http_header("text/html");
return OK if $r->header_only;
$r->print("hello!\n<br/>");
$r->rflush;
if (-f $r->filename or -d _) {
$r->print($r->uri, " exists!\n");
}
return OK;
}
1;
__END__
http://wiki.nginx.org/HttpPerlModule より
nginx: HttpPerlModule • mod_perl2ではなくmod_perl1の構文の 影響を強く受けている ($r->send_http_headers()等に見られる) • nginx設定ファイルの書き方については 割愛→前述のWikiに詳細がある
割愛したこと • Perl CGIの高速化環境の詳細 • Apache2.4環境でのmod_perl→現時点 (2012/09)で正式にサポートしていない • Apache2.4 event MPM環境での mod_perl→上記同様
これからのApache • Apache2.4でmod_perlがサポートされた ら、event MPM上でmod_perl動かしたい • Apacheはこれからも進化していきます • mod_lua, mod_sedがApache2.4でコアモ ジュール入り、しかしExperimental扱い
Questions?
ご清聴 ありがとうございました