日別アーカイブ: 2012年8月3日

Apache上のPerl FastCGIはCustomLogにデータを書くことができるか?ついでにmod_perlでのお話

こんにちは、Apache mod_perl が大好物の おがた (@xtetsuji) です。

そういえば、2012年に開催されるYAPC::Asia Tokyo 2012にトーク「モダンmod_perl入門」を応募しました。この記事が気に入りましたら、ぜひとも「イイね!」お願いします。

mod_perlに偏執的なのも良くないなと思って、最近はFastCGIやPSGI/Plack以降のWAFも勉強しています。Perlから外に出られていないのがまだまだといったところですが…;。

それでも好きが講じて、Twitterを使って日々mod_perlの情報を収集しているのですが、今日こんな会話を見つけました。

この会話、要約するに

  • ApacheのFastCGI(mod_fastcgi)を使ってPerlスクリプトを動作させている
  • PHPのapache_note()関数のようにApacheでデータをログに乗せたい → たぶん LogFormat ディレクティブで “%{Foobar}n” 書式を使って CustomLog ディレクティブに指定したログにデータを書きたい
  • mod_perlは使いたくない → 嫌わないで(´Д⊂ヽ

FastCGIの仕様書や、FastCGIを使ったプログラムを書いたり読んだりしたことはありますが、私はそれほどFastCGIの仕様はわかっていません。ただ、当初これはFastCGIでは無理ではないかと思いました。

  • FastCGIは永続環境であるもののCGIの思想を踏襲しているわけで、Apacheとは独立した仕様であり(Nginxやlighttpdでも動きますし)、Apache HTTPリクエストフェーズでの情報交換を目的としたApache独自の「Apacheノート」にアクセスすることは出来ないだろう
  • FastCGI、今回はApacheのmod_fastcgi自体がこの要望を叶えられないのであれば、各種CGIとなるプログラム言語のライブラリレベルで努力しても、少なくとも普通の実装では無理だろう

環境変数ならと考えてはみたものの、perldoc FCGI を読んでみても実際に試してみても、use FCGI; して得られた my $request = FCGI::Request(); を試行錯誤していじってみても、環境変数をApacheリクエスト処理の後続に位置しているログ処理フェーズに伝えることはできませんでした。…;というかそんなのは当たり前ですね。詳しく説明できない程度の知識しかないのがもどかしいですが、FastCGIはApache本体とは独立したコンテナのようなデーモン(サーバ)を作るわけですから。気の利いたソケット(詳しくないけど言ってみたかっただけ)で両者が繋がっていれば話は変わってくるかもしれませんが、最初から勝算はありませんでしたし、うまくもいきませんでした。

perldoc FCGI をさらに読んでも、環境変数はまだしも、Apacheノートに関する記述は完全にありませんでした。敗北が見えはじめました。

基本に戻ってLogFormatのカスタムログ書式を復習してみました。ApacheのサイトにあるLogFormatのカスタムログ書式のマニュアルを読んでみると、任意データを受け取れそうな “%{Foobar}?” といった書式はこれだけありました。

  • %{Foobar}i:入力ヘッダ “Foobar” の内容
  • %{Foobar}n:Apacheノート “Foobar” の内容 (公式サイト上では「メモ」と書いていますが、この記事では「Apacheノート」で統一します)
  • %{Foobar}o:出力ヘッダ “Foobar” の内容

入力ヘッダはいじりようがない(FastCGIが動作するリクエストフェーズのずっと前の、Apacheのヘッダ解釈フェーズで既に処理されている)ので除外。Apacheノートも、上記考察から除外。

最後に残ったのは出力ヘッダ。「あ、出力ヘッダはFastCGIからApacheを通るし、もしかしたらいけるんじゃね?」と思って試してみました。

custom-output-header.fcgi

#!/usr/bin/perl
# ogata 2012/08/03

use strict;
use warnings;

use FCGI;

my $request = FCGI::Request();

while ( $request->Accept() >= 0 ) {
    print "Content-type: text/plainrn";
    print "X-Tetsuji: Hello! World.rn";
    print "rn";
    print "It is fine.rn";
}

LogFormatの設定

LogFormat "%h %l %u %t "%r" %>s %O "%{Referer}i" "%{User-Agent}i" "%{X-Tetsuji}o"" combined_xtetsuji

手元のApache2+mod_fastcgiにVirtualHostを作って、この combined_xtetsuji を CustomLog で使うようにして、custom-output-header.fcgi を叩いてみました。

以下がそのアクセスログ(IPアドレスは何となく伏せました)

xxx.xxx.xxx.xxx - - [03/Aug/2012:00:08:28 +0900] "GET /custom-output-header.fcgi HTTP/1.0" 200 245 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.57 Safari/537.1" "Hello! World."

お、最後に “Hello! World.” 出てる!

結果的に、出力ヘッダ “%{Foobar}o” を LogConfig ディレクティブに使えば、記録したいデータを出力ヘッダに含めることで、FastCGI でも Apache CustomLog ディレクティブに指定したログにデータが記録できることがわかりました。

…;が、この方法は、わざわざヘッダを見るユーザには記録したいデータが丸見えという明らかな欠点がありますね。これはちょっと…;

Apache FastCGIを使う限り、会話に登場した方が本当に満足する方法があるのか。もしくは上記で良いのか、ちょっとわかりません。私にはこれが限界でした。

あと一つ!mod_perlはPerl CGIの高速化環境として見ても、それほど悪いものではないですよ。FastCGIもmod_perlも永続環境という意味では同じような魅力と問題を合わせ持っていると思います。インフラエンジニアの専門家の方には「Apache自体はスリムであれ」等、鋭いご指摘等があるかもしれませんが。そういったご意見もぜひご教示いただきたいです。

ちなみに、Perl CGIの高速化環境 (PerlHandler Apache::Registry (mod_perl1) / PerlResponseHandler ModPerl::RegistryPrefork (mod_perl2)) のmod_perlでAapcheノートにデータを読み書きする方法は、ざっくり以下です。

  • use CGI; # CGI.pm は Perl CGIの高速化環境下のmod_perl{1,2}をサポートしている
  • my $cgi = CGI->new(); # 普通にインスタンス作成
  • $cgi->r というメソッドが用意されていて、これでmod_perlリクエストオブジェクト(Apache::Request (mod_perl1) / Apache2::RequestRec (mod_perl2)) を取得することができる
  • $cgi->r->notes->get(“BuzzMemo”); # Apacheノート “BuzzMemo” の値を取得
  • $cgi->r->notes->set(“BuzzMemo” => “This is fine”); #  Apacheノート “BuzzMemo” の値を設定

実際にmod_perlハンドラを生で書く場合には幾つかの注意点があったりしますが、今回は割愛(需要あるかな?)。CGI.pmではその部分をなんとなく吸収してくれています。ただひとつ注意点は、CGI.pmのドキュメント perldoc CGI では r というメソッドには一切触れていない、つまり非公開メソッドであること。とても歴史の古いモジュールですので、突然次の日に使えなくなる可能性は限りなく低いとは思いますが、使う場合はその点ご留意ください。

そんな部分も含め、mod_perlの興味深い世界に興味を持っていただいた方は、しつこいですがトーク「モダンmod_perl入門」にぜひ「イイね!」よろしくお願いします!YAPC::Asia Tokyo 2012 会場でのトークや(トーク動画とともに後日公開もされる)資料で、もっと興味深い世界をご紹介します。