PersistentPerlでCGI.pm(ry 原因と対処策
PersistentPerlを使うと2回目以降のリクエストでCGI.pmが-no_xhtmlを忘れてしまう件の、原因と対処策。
- PersistentPerl 2.22
- CGI.pm 3.15
CGI.pmにデバッグプリントを埋め込みまくって前回の再現ケースを実行すると、こうなる。
$ touch test.pl; ./test.pl 1; ./test.pl 2 ##### >initialize_globals XHTML= ##### <initialize_globals XHTML=1 ##### >import XHTML=1 ##### <import XHTML=0 ##### 1 ##### >new XHTML=0 ##### <new XHTML=0 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html lang="en-US"><head><title>Untitled Document</title> </head> <body> ##### 2 ##### >new XHTML=1 ##### <new XHTML=1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US"> <head> <title>Untitled Document</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> </head> <body>
- -no_xhtmlの設定値は$XHTMLというパッケージ変数に格納されている。1ならXHTML、0ならHTML。
- use CGIすると最初に内部でinitialize_globals()が呼ばれ、$XHTMLが1に設定される。ここは固定値。
- 次にimport()が呼ばれユーザ設定が読み込まれる。-no_xhtmlを指定していればここで$XHTMLは0になる。
- 初回の試行は意図どおり。
- 2回目、CGI->new()をした時点で$XHTMLが1になってしまっている。
initialize_globals()とimport()はパッケージをロードしたときに一度しか呼ばれないものなので、2回目の試行時には実行されない。にもかかわらずこのように$XHTMLの値が書き換わってしまう。
1回目と2回目の試行の間に$XHTMLを勝手に書き換えているのは誰か。実は…
$ cat src/perperl_perl.c 〜 static int onerun(int single_script) { 〜 /* Hack for CGI.pm */ my_call_sv(get_perlvar(&PERLVAR_RESET_GLOBALS));
PersistentPerl本体!そんなのわかるか。
ここのmy_call_sv()によって呼ばれる処理は、CGI.pmの以下の部分。
sub _reset_globals { initialize_globals(); }
ということで原因をまとめると、
- CGI.pmはパッケージ変数を書き換えながら動く行儀の悪いモジュール。
- PersistentPerlは実行の都度_reset_globals()を呼んでパッケージ変数を初期化することで、これに無理やり対処した。ちなみにmod_perlの場合は、CGI.pm側に_reset_globals()を呼ぶルーチンが入っている。ずるい。
- ところがCGI.pm 3.15では、_reset_globals()を実行しただけでは初期化になっていない。あわせてimport()も実行する必要がある。
- そのため、2回目以降は開発者の意図と異なる設定で動いてしまっている。
となる。
対処方法は何でもいいんだけど、基本的にはアプリケーション側で対処するのがよいかと。PersistentPerl本体やCGI.pmにパッチをあてると維持管理がめんどくさいので。
#!/usr/bin/perperl -w print "##### $ARGV[0]\n"; use CGI; CGI->import(-no_xhtml); my $cgi = CGI->new(); print $cgi->start_html();
要するに毎回正しく初期化しなさいと、それだけ。
$ touch test.pl; ./test.pl 1; ./test.pl 2 ##### >initialize_globals XHTML= ##### <initialize_globals XHTML=1 ##### >import XHTML=1 ##### <import XHTML=1 ##### 1 ##### >import XHTML=1 ##### <import XHTML=0 ##### >new XHTML=0 ##### <new XHTML=0 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html lang="en-US"><head><title>Untitled Document</title> </head> <body> ##### 2 ##### >import XHTML=1 ##### <import XHTML=0 ##### >new XHTML=0 ##### <new XHTML=0 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html lang="en-US"><head><title>Untitled Document</title> </head> <body>
やっと直った。