2005-06-18

mod_perlの実験(1) - Perlスクリプトの実行環境

まず、Perlの実行環境について、CGIとmod_perlとの違いを見てみましょう。

env.cgi

#!/usr/bin/perl -w
use strict;
use Cwd;

print "Content-type: text/plain\n\n";

print "< Environment Val. >\n";
print "SERVER_SOFTWARE: $ENV{SERVER_SOFTWARE}\n";
print "MOD_PERL: $ENV{MOD_PERL}\n\n";

print "< \@INC >\n";
print join "\n",@INC;
print "\n\n";

print "< getcwd() >\n";
print "@{[getcwd()]}\n\n";

print "< Package Name >\n";
print __PACKAGE__,"\n\n";

print "< PID/TID >\n";
print "PID: $$";
if (exists $ENV{MOD_PERL}){
  require Apache::MPM;
  if (Apache::MPM->is_threaded) {
    require APR::OS;
    my $tid = APR::OS::current_thread_id();
    print " (TID: $tid)";
  }
}

env.cgiの実行結果は以下のようになります。

CGIでの実行結果

< Environment Val. >
SERVER_SOFTWARE: Apache/2.0.53 (Win32) mod_perl/1.999.21 Perl/v5.8.6
MOD_PERL: 

< @INC >
C:/usr/lib
C:/usr/site/lib
.

< getcwd() >
D:/WWWRoot/mod_test

< Package Name >
main

< PID/TID >
PID: 5424

mod_perl(ModPerl::PerlRun)での実行結果

< Environment Val. >
SERVER_SOFTWARE: Apache/2.0.53 (Win32) mod_perl/1.999.21 Perl/v5.8.6
MOD_PERL: mod_perl/1.999.21

< @INC >
D:/WWWRoot/mod/mod_test
D:/WWWRoot/mod
C:/usr/site/lib/Apache2
C:/usr/lib
C:/usr/site/lib
.
C:/usr/Apache2

< getcwd() >
C:/usr/Apache2

< Package Name >
ModPerl::ROOT::ModPerl::PerlRun::D_3a_WWWRoot_mod_mod_test_env_2ecgi

< PID/TID >
PID: 1400 (TID: 812)

※ModPerl::Registry の場合は、パッケージ名の PerlRunが Registryになります。その他は同じです。

環境変数

SERVER_SOFTWAREの値はmod_perlがロードされいる状態では両者ともに変わりありません。mod_perlのロードされていない状態では以下のようになります。

SERVER_SOFTWARE: Apache/2.0.53 (Win32)

mod_perlの場合は環境変数にMOD_PERLが加わります。

MOD_PERL: mod_perl/1.999.21

CGIではMOD_PERLは存在しません。この事を利用してmod_perl特有のコードを追加する事ができます。

if (exists $ENV{MOD_PERL}){
  #mod_perl特有のコード
}

@INC

mod_perlの場合は、@INCにApache2モジュールがC:/usr/site/lib/Apache2とC:/usr/Apache2を加えています。C:/usr/site/lib/Apache2はmod_perlが使用するモジュールパスで、C:/usr/Apache2はApacheのインストールフォルダです。残りの追加パスはセットアップファイルで追加したuse libに対応しています。

D:/WWWRoot/mod/mod_test
D:/WWWRoot/mod
C:/usr/site/lib/Apache2
C:/usr/lib
C:/usr/site/lib
.
C:/usr/Apache2

カレントディレクトリ

CGIのカレントディレクトリはリクエストされたスクリプトが存在する物理フォルダを指していますが、mod_perlの場合は、Apacheのインストールフォルダになっています。

C:/usr/Apache2

mod_perl環境下でカレントディレクトリをCGIと同じに所に変更するには、以下のようなコードを書く必要があります。

BEGIN {
  if (exists $ENV{MOD_PERL}){
    if (Apache->request->filename() =~ m!(.*[/\\])!){
      require Cwd;
      Cwd::chdir(substr($1,0,-1));
    }
  }
}

但し、このカレントディレクトリはmod_perl環境で共通に使用される値なので、後で他のスクリプトが変更している可能性があります。

スクリプトのパッケージ名

CGIの場合、実行されるPerlスクリプトのパッケージ名(名前空間)は当然mainですが、mod_perlの場合は以下のようになります。

ModPerl::ROOT::ModPerl::PerlRun::D_3a_WWWRoot_mod_mod_test_env_2ecgi
ModPerl::ROOT::ModPerl::Registry::D_3a_WWWRoot_mod_mod_test_env_2ecgi

パッケージ名の前半部分がスクリプトを起動したレスポンスハンドラの名前空間を表し、後半部分はスクリプトの物理パスを表しているように見えます。このようにmod_perlではリクエストされるスクリプトの名前空間の衝突をさけるために、スクリプト毎にパッケージ名(名前空間)を変えています。

CGIの場合は暗黙の名前空間(main)を使用できましたが、mod_perlではできません。コードがコンパイルされる時、CGIのように暗黙の名前空間を使用していると思わぬ事態に遭遇します。例えば、Apache::Reloadを介してモジュールをリロードする場合、暗黙の名前空間はmainではなくApache::Reloadの名前空間になります。これはApache::Reloadがモジュールをrequire()するためです。mod_perlを使用した開発ではできる限り明示的な名前空間を使う事を勧めます。

プロセス

CGIの場合はリクエストの度に異なったプロセスIDになります。これはサーバ(Apache)がリクエストの度にPerlのインタプリタ(perl.exe)を起動しているからです。一方、mod_perlの場合は、プロセスIDが変化しません。サーバはスタートアップ時に子プロセスを起動し、リクエスト処理はこの子プロセスで繰り返し行われる為です。子プロセス起動の過程でPerlも起動されます。mod_perlので実行結果で表示されているプロセスIDはこの子プロセスのIDです。コマンドプロンプトでtasklistコマンドを実行して見て下さい。

C:\>tasklist /svc
イメージ名           PID    サービス
===================== ====== =============================================
Apache.exe           1624   Apache2
  ・
  ・
Apache.exe           1400   N/A

※/svcはサービス名を表示するtasklistコマンドのオプションです。

サービス名がApache2の方が親プロセス、他方が子プロセスです。子プロセスはサーバが停止するか、親プロセスにより終了させられるまで走り続けます。

CGIではリクエスト処理を終える為にexit()を使用してプロセスを終了します。mod_perlではexit()を使ってもプロセスは終了しません。これはmod_perlがexit()を ModPerl::Util::exitにオーバライドしているからです。ModPerl::Util::exitはリクエスト処理を終了しますが、プロセスは終了しません。プロセスを終了するにはオリジナルのCORE::exit()を使用します。この場合、子プロセスは終了し、親プロセスは子プロセスを再起動します。子プロセスを再起動すると余分な負荷がサーバに掛かります。特別な場合でない限り、子プロセスを終了すべきではありません。

余談ですが、evalの中のexit()の使用には注意が必要です。CGIの場合はevalの中でexit()を使用するとプロセスが終了しますが、mod_perlでオーバーライドされたexit()はevalの中でdie()のように振舞います。evalの中でexit()を使うと、evalはその時点で中断されますがリクエスト処理は続いています。evalがexit()で中断された場合、$@には以下のようなエラーメッセージが入っています。

ModPerl::Util::exit: (120000) exit was called at D:/WWWRoot/mod/mod_test/ ・・・・

これを利用して、die()と同じようなエラートラップができます。

eval {
  exit();
};
exit if $@ =~ /^ModPerl::Util::exit/;

これは簡易的な方法です。厳密なトラップの方法は、以下のURLを参照して下さい。

http://perl.apache.org/docs/2.0/api/ModPerl/Util.html



最終更新のRSS Last-modified: Sat, 18 Jun 2005 17:00:01 JST (4535d)