CGIプログラミング(3) - アクセスカウンタ

以下のプログラムはアクセスカウンタとして動作する。 CGIプログラムは、アクセスするたびに実行されて、HTMLとしての結果を返すと終了するので、アクセスカウンタのように、以前の状態(アクセス数)が必要な場合は、それをどこかに記憶しておかなければならない。一般にCGIプログラムでは、ファイルに記録することで対処することが多い。

counter.cgi - アクセスカウンタのプログラム
 1: #!/usr/bin/env perl
 2: print "Content-Type: text/html\n\n";
 3: $cfile="counter.dat";
 4: # カウンタの値を読み込む
 5: if (open(FH, $cfile)) {
 6:     $counter = <FH>+1;
 7:     close(FH);
 8: } else {
 9:     $counter = 1;
10: }
11: # カウンタの値を書き込む
12: open(FH, ">" . $cfile) or die "Cannot write counter file!";
13: print FH $counter;
14: close(FH);
15: # カウンタの値をHTML形式で表示する
16: print "<HTML><BODY>\n";
17: print "counter=$counter\n";
18: print "</BODY></HTML>\n";

このプログラムは、counter.dat というファイルにカウンタの値(整数値)を格納し、 実行されるたびにカウンタの値を1つ増やしている。このプログラムは大まかに4つの処理から成り立っている。

以下は、それぞれの詳細について説明する。

httpのヘッダを出力する

前の例と同様にhttpのヘッダで、html形式であることを標準出力に出力する。

ファイルからカウンタの値を読み込み、1を加える

カウンタの値を入れておくファイルcounter.datを読み込む。 ファイルを読み込むには、まず「ファイルを開く」という操作をする。 ファイルを開くにはopenを使う。

open(FH, ファイル名);

FHはファイルハンドラといい、開いたファイルを指定するための特殊な変数だと考えれば良い。名前は変数と同じようにつけられるが、大文字で書くのが通例である。 ファイル名には、文字列でこれから開くファイル名を指定する。この場合は、3行目で"counter.dat"を$cfileに代入して、その$cfileを使っている。つまり、5行目では"counter.dat"を開こうとする。

openはファイルを開くのに成功すると戻り値としてTrue(真)を返し、失敗するとFalse(偽)を返す。ここでは、if文でopenの結果がTrueであれば、6,7行目を、Falseであれば9行目を実行するようにしている。

ファイルが開けない場合とは、例えば、ファイルが存在しない場合や、ファイルの読み取り許可がない場合である。

ファイルのオープンに成功したら、7行目でファイルから一行読み込む。 一行読み込むには<FH>と書く。この一行読み込んだ内容に1を足して、$counterに代入している。

ちなみにPerlで標準入力から一行読み込むには、<>と書く。 ただし、標準入力の場合は、「ファイルを開く」という操作が必要ない(最初から開いている)のに対して、ファイルの場合は読み込む前に必ずファイルを開く操作が必要なところが違う。

7行目では、「ファイルを閉じる」という作業をしている。 一度開いたファイルは使い終わったら「閉じる」ことが必要である。 実はプログラムが終了したら、自動的に閉じてくれるのだが、 このプログラムのように再度開くこともあるので、きちんと閉じる習慣をつけるべきである。

ファイルにカウンタの値を書きこむ

このプログラムでは、counter.dat というファイルにカウンタの値を記録して、 次回にcounter.cgiを読んだ時に使う。つまり、counter.datに書き込まないことにはうまく機能しない。

ファイルに書き込むのは、ファイルを読み込む操作に非常に似ている。 12行目でファイルを開く。読み込む時と違うのは、">" . がついていることである。これでファイル名の前に>をつけることなる。つまり、この場合ファイル名は$cfileの値、つまり "counter.dat" であるかたら、">counter.dat" となるわけである。 このようにファイル名の前に>をつけると、書き込み用にファイルを開く。 なお、書き込むためのファイルがすでに存在する場合には、その内容を全て消してから書き込む。 この場合、counter.datが存在していても、counter.datの内容は全て消される。 また、openの後に or die "Cannot write counter file!"; というのがついている。これは、ファイルのオープンに失敗したら、"Cannot write counter file!"というメッセージを表示してプログラムを終了する動作をする。以下のプログラムを簡単に書いたPerl独特の書き方である。

if (!open(FH, ">" . $cfile)) { die "Cannot write counter file!"; }

13行目では、ファイルカウンタの値を書きこんでいる。 ここではprintを使っている。 画面(標準出力)に出力するには、printを使ったが、 ファイルに書きこむ場合は、print FH という風に print の後にファイルハンドラを書く。 14行目で、ファイルを閉じている。 ファイルを閉じるのを忘れないように心がけるべきである。

HTMLとしてカウンタの値を出力する

ここは、以前に習った通りである。 HTML形式で、「counter=カウンタ値」という内容を表示している。 17行目には文字列中に変数counterの値を入れ込んでいるが、 print "counter = ", $counter, "\n";としても同じである。

このプログラムの留意点

このプログラムには、1つ問題がある。アクセスが集中すると、カウンタの値が1に戻ってしまう。この問題を解消するには排他制御を行う必要があり、普通はファイルのロックという作業を行う。プログラムがさらに複雑になるので、ここではそこまではやらないが、実用的なCGIプログラムを作成するには欠かせない事項である。興味のある人は関連する文献を探して勉強することを勧める。

HTMLファイルの中にカウンタを埋め込みたい

以上で、Webページを何回アクセスしたかを示すアクセスカウンタが作成できたが、 通常アクセスカウンタは、長いHTMLファイルの中に埋め込んで使いたいものである。 その方法にはいろいろとあるが、ここでは、CGIプログラムを呼び出して、 そのプログラムの中から表示したいhtmlファイルを読み込み、 指定した場所にアクセスカウンタを埋め込んで表示するようにする。

CGIの仕組みそれ自身は、このような用途にはあまり向いていない。 SSIなどを使うのが便利だが、セキュリティの問題になりやすいので薦めない。 HTMLに組み込む場合は、PHPやeRubyという仕組みなどが向いている。

以下では読み込むファイルを、esample.html とし、そのファイルの中に、 ACCESS_COUNTERという行があれば、その行の内容を アクセスカウンタの値に付け替えるようにしている。
※ その行にはACCESS_COUNTERのみを書くようにすること。ACCESS_COUNTER 以外の文字列は消えてしまう。

ecounter.cgi - HTMLの中にアクセスカウンタを埋め込む
#!/usr/bin/env perl
print "Content-Type: text/html\n\n";
$cfile="ecounter.dat";
$hfile="esample.html";
$match_str = "ACCESS_COUNTER";
# カウンタの値を読み込む
if (open(FH, $cfile)) {
    $counter = <FH> + 1;
    close(FH);
} else {
    $counter = 1;
}
# カウンタの値を書き込む
open(FH, ">" . $cfile) or die "Cannot write counter file!";
print FH $counter;
close(FH);
# HTMLファイルを読み込み、その内容を出力する
open(FH, $hfile);
while (<FH>) { # ファイルから1行読み込む
    if ($_ =~ /$match_str/) {
        $_ = $counter . "\n";
    }
    print $_; # 1行分を標準出力に出力
} 
close(FH);

esample.html - 使用するHTMLファイルの例

課題

  1. 上記counter.cgiを使ってアクセスカウンタのCGIを実行せよ。
  2. 上記ecounter.cgiを使って、自分のホームページにアクセスカウンタを埋め込むCGIを作成せよ。ただし、ホームページのファイルはコピーして別に用意してよい。
  3. 上記ecounter.cgiを元に、表示するHTMLの中にCURRENT_TIMEという行があれば、その行を現在の時刻に入れ替えて表示するCGIを作成せよ。

東京情報大学情報システム学科
大見 嘉弘(Yoshihiro OHMI)