mod_perlハンドラを書く
Rev.3を表示中。最新版はこちら。
このテキストは
mod_perl2.0ドキュメントの"Writing mod_perl Handlers and Scripts"(http://perl.apache.org/docs/2.0/user/coding/coding.html)の和訳です。
現在編集中
目次
- 説明
- 前提
- Where the Methods Live
- Techniques
- Goodies Toolkit
- Code Developing Nuances
- Integration with Apache Issues
-
- HTTPレスポンスヘッダ
- HTTPレスポンスBodyの送信
- シグナルハンドラの使用
-
# Integration with Apache Issues
* HTTP Response Headers
o Generating HTTP Response Headers
o Forcing HTTP Response Headers Out
* Sending HTTP Response Body
* Using Signal Handlers
# Perl Specifics in the mod_perl Environment
* BEGIN Blocks
* CHECK and INIT Blocks
* END Blocks
* Request-localized Globals
* exit
# ModPerl::Registry Handlers Family
* A Look Behind the Scenes
* Getting the $r Object
# Threads Coding Issues Under mod_perl
* Thread-environment Issues
* Deploying Threads
* Shared Variables
# Maintainers
# Authors
説明※
この章ではmod_perlのコーディングの詳細、通常のPerlコーディングとの違いを扱います。その他のほとんどのPerlコーディングに関する情報はperlのmanページや書籍で取り扱われています。
前提※
原文なし
Where the Methods Live
mod_perl 2.0では多くのモジュールに跨ってメソッドが存在します。これらのメソッドを使うためにメソッドを含むモジュールは最初にロードしておかないといけません。そうでないと、mod_perlは「メソッドを見つけられない」とエラーにします。どのモジュールが必要かを探すにはModPerl::MethodLookup モジュールが使えます。
Techniques
メソッドハンドラ※
関数ハンドラに加えてメソッドハンドラを使うことができます。メソッドハンドラは継承のメリットを生かしたコードを書きたい場合に便利です。mod_perl2のメソッドとしてハンドラを作る場合、method属性を使ってください。
属性の詳細についてはPerlのmanページを参照してください(perldoc attributes)。
例:
package Bird::Eagle; @ISA = qw(Bird); sub handler : method { my ($class_or_object, $r) = @_; ...; } sub new { bless {}, __PACKAGE__ }
そして、これを以下のようにして登録します。
PerlResponseHandler Bird::Eagle
mod_perlはハンドラがmethod属性を持っている場合は、2つの引数をハンドラに渡します。上記のコードにもあるようにオブジェクトもしくはクラス名(どのように呼び出されたかに依存します)とRequestオブジェクトです。
以下のようにClass->methodのような形式が使われた場合は、
PerlResponseHandler Bird::Eagle->handler;
method属性は必要ありません。上の設定例ではhandler()メソッドはクラス(静的)メソッドとして呼び出されます。
メソッドを呼び出すのにオブジェクトを使うこともできます。例えば、
<Perl> use Bird::Eagle; $Bird::Global::object = Bird::Eagle->new(); </Perl> ... PerlResponseHandler $Bird::Global::object->handler
この例では、hanlder()メソッドはグローバルオブジェクト$Bird::Global::objectのインスタンスメソッドとして呼び出されます。
クリーンアップ※
さまざまなフェーズの終わりで発生するクリーンアップ処理をアレンジできます。これを行うのにENDブロックは使えません。なぜなら、ENDブロックはインタプリタが終了するまで実行されないからです(Registryハンドラは例外です)。
モジュール作成者は各HTTPリクエストの後にクリーンアップ処理が必要な場合は、PerlCleanupHandlerを使うべきです。
その他のタイミングでクリーンアップ処理を行う必要がある場合は、プールオブジェクトのcleanup_registerメソッドでクリーンアップコールバックを登録することができます。使用例をいくつか示します。
サーバーのシャットダウンやリスタート時に何か行いたい場合は、startup.plでserver_shutdown_cleanup_register()で登録したクリーンアップハンドラを使ってください。
#PerlPostConfigRequire startup.pl use Apache2::ServerUtil (); use APR::Pool (); warn "parent pid is $$\n"; Apache2::ServerUtil::server_shutdown_cleanup_register((\&cleanup); sub cleanup { warn "server cleanup in $$\n" }
この方法は、サーバーが停止したり、再起動する時に行うべきサーバー全体のクリーンアップ処理を行うのに便利です。
接続フェーズの終わりにクリーンアップを行う場合は、コネクションプールオブジェクトにクリーンアップコールバックを割り当てます。
use Apache2::Connection (); use APR::Pool (); my $pool = $c->pool; $pool->cleanup_register(\&my_cleanup); sub my_cleanup { ... }
あなた自身のプールオブジェクトを作成してコールバックを登録することもできます。これはオブジェクトが破壊された時に呼び出されます。
use APR::Pool (); { my @args = 1..3; my $pool = APR::Pool->new; $pool->cleanup_register(\&cleanup, \@args); } sub cleanup { my @args = @{ +shift }; warn "cleanup was called with args: @args"; }
この例ではクリーンアップコールバックは$poolがスコープの外に出て破壊された時に呼び出されます。これはオブジェクト指向のDESTROYメソッドに非常に似ています。
Goodies Toolkit
環境変数※
mod_perlは次の環境変数を設定します。
- $ENV{MOD_PERL} - mod_perlのバージョンが設定されます
例:
mod_perl/2.000002
$ENV{MOD_PERL}が存在しなければ、たぶんmod_perl配下で動作していません。
die "I refuse to work without mod_perl!" unless exists $ENV{MOD_PERL};
どのバージョンが使われているのかチェックするには、次のテクニックを使うとよいです。
use mod_perl; use constant MP2 => ( exists $ENV{MOD_PERL_API_VERSION} and $ENV{MOD_PERL_API_VERSION} >= 2 ); # die "I want mod_perl 2.0!" unless MP2;
mod_perlは次の環境変数をエクスポートします(それらが設定されていれば)。
- PATH - 実行可能ファイルのパス
- TZ - タイムゾーン
これらの環境変数は%ENVでアクセスできます。
スレッド化MPMか否か※
実行環境がスレッド化されたMPMかそうでないかでコードが異る動作をする必要があるならApache2::MPM->is_threadedクラスメソッドが使えます。
例:
use Apache2::MPM (); if (Apache2::MPM->is_threaded) { require APR::OS; my $tid = APR::OS::current_thread_id(); print "current thread id: $tid (pid: $$)"; } else { print "current process id: $$"; }
このコードはスレッド化されたMPM配下ではカレントスレッドIDを表示します。そうでなければプロセスIDを表示します。
MPM固有のコードを書く※
あなたがCPANモジュールを書くなら、全てのMPMで動作しないコードを書くのはまずい考えです。開発者は全てのMPMで動作するコードを書くよう努力すべきです。異るMPM環境で異ることを行うのはまったく立派なことです。
あなたがCPANモジュールを開発しないなら、特定のMPMで動作すればあなたのプロジェクトにとって十分でしょう。
use Apache2::MPM (); my $mpm = lc Apache2::MPM->show; if ($mpm eq 'prefork') { # prefork-specific code } elsif ($mpm eq 'worker') { # worker-specific code } elsif ($mpm eq 'winnt') { # winnt-specific code } else { # others... }
Code Developing Nuances
修正されたモジュールの自動リロード※
META: need to port Apache2::Reload notes from the guide here. but the gist is:
PerlModule Apache2::Reload PerlInitHandler Apache2::Reload #PerlPreConnectionHandler Apache2::Reload PerlSetVar ReloadAll Off PerlSetVar ReloadModules "ModPerl::* Apache2::*"
以下のハンドラを使ってください。
PerlInitHandler Apache2::ReloadHTTPプロトコルハンドラをデバッグしたいなら、以下のようにしてください。
PerlPreConnectionHandler Apache2::Reload
我々が自分達のモジュールを以下のような形式にする場合の注意です。
package Apache2::Whatever; use strict; use warnings FATAL => 'all';
FATAL => 'all' は全ての警告をfatalエラーとして上げます。Apache2::Whateverが変更されてApache2::Reloadによって再読み込みされた時、リクエストは中断されてしまいます。ですから、もしあなたがこの非常に健全な方法にしたがってApache2::Reloadを使いたいなら以下のようにエラーチェックの厳密さを変更してください。
use warnings FATAL => 'all'; no warnings 'redefine';
redifne 警告を取得したいが、non-fatalにレベルを落とす場合は次のようにしてできます。
use warnings FATAL => 'all'; no warnings 'redefine'; use warnings 'redefine';
Perl 5.8.0なら1行で全て行えます。
use warnings FATAL => 'all', NONFATAL => 'redefine';
もしあなたのコードが古いバージョンのPerlで使われるかもしれないのなら、この新しいは使わないほうがよいでしょう。
さらなる情報はperllexwarn manページを参照してください。
Integration with Apache Issues
以下ではmod_perl開発者に関係するApacheの振る舞いの詳細について議論していきます。
HTTPレスポンスヘッダ※
HTTPレスポンスヘッダの生成※
HTTPレスポンスヘッダを生成する最もよい方法はmod_perl APIを使うことです。いくつかの共通のヘッダには専用のメソッドがあります。その他のものも、headers_outテーブルを直接操作することで設定できます。
例えば、Content-typeヘッダを設定する場合は、$r->content_typeを呼び出すべきです。
use Apache2::RequestRec (); $r->content_type('text/html');
カスタムヘッダMy-Headerを設定する場合は、以下のようにしてください。
use Apache2::RequestRec (); use APR::Table; $r->headers_out->set(My-Header => "SomeValue");
もし、registryスクリプトの中であれば、Apache2::RequestRecオブジェクトにまだアクセスできます。
レスポンスヘッダを出力することによってヘッダを作成するもっと遅い方法を採ることもできます。この方法はPerlOptions +ParseHeadersが有効な場合のみ動作します。
print "Content-type: text/html\n"; print "My-Header: SomeValue\n"; print "\n";
この方法はApacheがヘッダを識別するためテキストをパースする必要があるので遅くなります。
またいくつかの制限があります(これから議論します)。
このアプローチでは、STDOUTファイルハンドルがprint後にデータをフラッシュしないようになっていることを確認しなければなりません(これはPerlの特殊変数$|に設定されています)。STDOUTが現在select()されており$|がそれに影響するものと仮定しています。
例えばこのコードは動作しません。
local $| = 1; print "Content-type: text/html\n"; print "My-Header: SomeValue\n"; print "\n";
$|が真だと最初のprint()呼び出し後、すぐにデータがフラッシュされます。データは内部のHTTPヘッダパーサーに送られます。そしてヘッダが"\n\n"で終わっていないように見えるのでエラーになります。解決方法としてはSTDOUTをすぐにフラッシュさせないようにする方法があります。
local $| = 0; print "Content-type: text/html\n"; print "My-Header: SomeValue\n"; print "\n";
変更をlocal()化していることに気をつけてください。これだと、他のコードに影響しません。
もし、あなたが1行づつヘッダを送信しそれらの合計長が8kを越えると、ヘッダパーサーの問題にまたぶつかるでしょう。mod_perlは8kバッファがいっぱいになるとデータをフラッシュするからです。この場合の解決法はヘッダを1行づつ出力しないようにすることです。変数にヘッダを格納して全体を一気に出力します。
mod_cgiを使う場合はこれらの問題は関係ありません。Perlによるフラッシュは無視されるからです。mod_cgiは外部プロセスへのパイプをオープンしプロセスから送られてきた出力を読み取るだけです。
この節の始めで説明したように、$rを使ってヘッダを設定するなら、これらの問題に遭遇することはありません。
最後にApacheにヘッダを送らせず、あなたが自分でヘッダ(non-parsed headers handlers)を送信したい場合は、$r->assbackwards メソッドを使ってください。スクリプト名がnph-で始まる場合は、registryハンドラはこのように動作することに注意してください。
HTTPレスポンスヘッダの強制出力※
Apache 2.0はHTTPレスポンスヘッダの送信を強制するメソッドを提供しません(Apache 1.3ではsend_http_header()が相当)。HTTPレスポンスヘッダはレスポンスBodyの最初のビットを見つけるとヘッダを作成するCore outputフィルタによりすぐに送信されます。レスポンスハンドラがボディデータの最初の部分を送信するときに、mod_perlの内部バッファやいくつかのoutputフィルタによりキャッシュされたりするかもしれません。レスポンスハンドラはレスポンスの送信にかかわっている全コンポーネントにデータ送信を伝えるために出力をフラッシュする必要があります。
例えば、もしハンドラが比較的時間のかかる処理(遅いDBの検索とか)を行う必要があり、すぐにクライアントが何も受信しないとタイムアウトするかもしれない場合、ハンドラの始めにContent-Typeヘッダを設定してすぐにフラッシュしたいかもしれません。
sub handler { my $r = shift; $r->content_type('text/html'); $r->rflush; # send the headers out $r->print(long_operation()); return Apache2::Const::OK; }
これが動作しないなら、サードパーティ製のoutputフィルタが設定されていないか確認してください。Improperly written filter はデータをフラッシュするのにコマンドを無視するかもしれません。