ロギングあの手この手

対応すべきリクエスト

というわけで nph-CGI によるハンドラを書くことになったわけである。nph-CGI とは、通常のCGIとは違って、「HTTP/1.1 200 OK」(リクエスト成功)以外のレスポンスも返すことができる...という低レベルのCGIである。「URLを書き換えろ!〜FormMailアタックへの対策」でも紹介しているが、通常のCGIがいきなり「Content-Type: MIMEタイプ」ヘッダだけを返してOKなのに対して、nph-CGI はすべてのヘッダを書かねばならないのである。これは要するに、通常のCGIが「リクエスト成功」のHTTPヘッダを自動的に生成して、コンテンツを返すのに対して、nph-CGI(Non-Parsed Header:非解析ヘッダ)は、そういう「余計なお世話」は何もしないのである。実際、アクセスされたURLに色々な問題があることもあるが、そういう責任までもロギングハンドラCGIは一手に引き受けなくてはならないのである!

では具体的に、どういうリクエストを処理しなければならないのだろう。これをまずまとめよう。メソッドは前提から、HEAD メソッドと GET メソッドの双方に対応しなければならないことは言うまでもない。

200 OK
これはいわゆる「成功」である。何も問題がなければ、ロギングCGIは指定されたURLを実パスとして解釈し、そのファイルを読み込んで標準出力に流せば良い。
304 Not Modified
これに対応するのは必須ではないが、アクセス効率が上がるので是非とも対応したいものである。要するに、すでにアクセスされたURLにもう一度アクセスし直すケースがあるが、その時の動作は「もしファイルに変更がなければ、キャッシュされたコンテンツを使う」と、効率が良い。実際ブラウザはこういう風に動作するのである。この時、次のようなリクエストを発行している。

GET /url/some.html HTTP/1.0
If-Modified-Since: Fri, 4 Jun 2004 23:46:51 GMT



この「If-Modified-Since」オプションヘッダに、先程アクセスした時間が入るのである。サーバはこの時間を見て、実際のファイルがその時間以降に修正されていれば、「200 OK」のレザルトコードとともにコンテンツを返すが、修正がなければ「304 Not Modified」ヘッダだけを返し、キャッシュをそのまま使って良いことをブラウザに教えるのである。ね、対応したくなったでしょ!
403 Forbidden
これはHTMLの場合には「ファイルが読めない」ケースにだけ返せば良い。実際には Rewrite 機能に渡る前に、ディレクトリで終るURLの場合には、正しく補完されたファイル名が渡るし、URLが間違っていてファイルがないケースは、先にそれを処理してくれている。だから、これを返すべきなのは、パーミッションの関係でファイルがあっても読めないケースだけとなる。対応するまでもないのだが、どうせ open して読めない場合に、エラー処理として書いた方が良いので、対応すべきである。
404 Not Found
これは良くお目にかかる、URLが間違っていてファイルが存在しないケースである。しかし、これは Rewrite ルールのレベルですでにファイルがない場合にはこれを返してしまっている。だから、存在しないファイルに対して、ロギングCGIが呼ばれることは原則としてありない。実質対応する必要はない。
405 Method Not Allowed
実際には、GET メソッドと HEAD メソッドに対応する必要がある。POSTメソッドはさすがにCGIは扱わないので、この「扱えないメソッド」を通知するレザルトコードを返せば良いのである。要するに、環境変数 REQUEST_METHOD が GET でも HEAD でもなければ、このレザルトコードが返る。

最初はCで書こうと思っていたが、これだけ対応するのはちょっと大変だ...結果として、Perl で書くことにする。また、nph-CGI にするので、nph-CGI であることをサーバに伝えるためには、ファイル名が「nph-」で始まる必要がある。だから、ファイル名は「nph-logger.cgi」とでもしよう。

まあ、参考のために、超単純な nph-CGI をお目にかけておこう。これは常に「404 Not Found」を返すものである。

#!/usr/bin/perl

# まずロギング
require '/home/sug/public_html/cgi-bin/loggerlib.pl';
&log('/cgi-bin/nph-jijo.cgi');

# メソッドへの対応
$method = $ENV{'REQUEST_METHOD'};
if ( $method eq 'POST' ) {
    read( STDIN, $query_string, $ENV{'CONTENT_LENGTH'} );
} else {
    $query_string = $ENV{'QUERY_STRING'};
}

# クエリの取得
@keypair = split( /&/, $query_string );
foreach $keyval (@keypair) {
	($key, $val) = split( /=/, $keyval );
	$val =~ tr/+/ /;
	$val =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack("C",hex($1))/eg;
	if( defined($FORM{$key}) ){
		$FORM{$key} = join( "\0", $FORM{$key}, $val );
	} else {
		$FORM{$key} = $val;
	}
}

# アクセス時間の取得
$Date = gmtime();
# 本来のアクセスURLの取得
$path = $FORM{'path'};

# 出力
print <<"EOM1";
HTTP/1.1 404 Not Found
Date: $Date
Server: Apache/1.3.29 (Debian GNU/Linux) PHP/4.3.4 mod_auth_pam/1.1.1
Connection: close
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML><HEAD>
<TITLE>404 Not Found</TITLE>
</HEAD><BODY>
<H1>Not Found</H1>
The requested URL /~sug/$path was not found on this server.<P>
</BODY></HTML>
EOM1

exit( 0 );

フツーのCGIと比較すると、出力で Content-Type 以外のヘッダも出している、という違いだけに過ぎない。まあ、どういうヘッダを出しているのか、は一目瞭然だ。勿論一番重要なのはレザルトコードである「HTTP/1.1 404 Not Found」であることは言うまでもない。

これを通常のファイルのフリをさせるには、RewriteRule を使うだけである。まあ、こんな .htaccess を書けば良い。

RewriteEngine on
RewriteBase /home/sug/public_html/secret
RewriteRule ^(.*)$ /~sug/cgi-bin/nph-404.cgi?path=secret/$1 [L]

これでサーバ上には存在しても、常に「404 Not Found」が返るURLが出来てしまうのである。





copyright by K.Sugiura, 1996-2006