この章では簡単なアクセスカウンタから やや高度なアクセスカウンタまでの作り方を解説します。
最近は各種アクセスカウンタのCGIソフトが出回り
プロバイダも提供しているので、
アクセスカウンタはCGIのもっとも平凡な使われ方として
広く普及しています。
CGIを自作しようと考える人の多くがまずアクセスカウンタを
自作しようと考えるでしょう。しかしまともにアクセスカウンタを
設計するには意外に多くの技術を必要とします。そのため本書で
アクセスカウンタの解説がこの章まで遅れました。
現在までのアクセスカウント数はもちろんファイルに保存します。 そのファイルのことを本書では単にカウントファイルと呼びことにしましょう。 そのカウントファイルの読み書きの仕方は自明なようで簡単ではありません。
カウンタCGIがカウントファイルにカウント数を書き込むので、 ファイルのpermissionやCGIのUIDに気を付けなければなりません。 CGIがカウントファイルに書き込めるようになるための方法には以下の4つの方法が あります。なるべく上側の方法を使うようにしてください。
-rwx-----x 1 naoki student count.cgi -rw------- 1 naoki student count.datカウントファイルの内容を他人に見られることはありません。
-rws-----x 1 naoki student count.cgi -rw------- 1 naoki student count.dat方法1と実質は同じです。
-rwx-----x 1 naoki student count.cgi -rw----rw- 1 naoki student count.dat誰のCGIでも書き込めるのであまりよろしくないです。
-rwx-----x 1 naoki student count.cgi -rw----r-- 1 nobody nogroup count.dat誰のCGIでも書き込めるのであまりよろしくないです。
カウンタCGIはカウントファイルのカウント数を読んで、その値をひとつ増やして、 カウントファイルに書き込まなければなりません。従って、ファイルは 読み書き両用モードで開かなくてはなりません。
FILE* fptr = fopen( "count.dat", "r+" );
複数のアクセスによって、カウンタCGIが時間的に近接して実行された場合、 カウントファイルの読み書きが衝突して 値の更新が正常に行われないことがあります。 これを防ぐために、ファイルを開いた直後に ファイルアクセスの排他ロックを掛けます。
flock( fileno(fptr), LOCK_EX ); // BSD系のUNIXの場合 lockf( fileno(fptr), F_LOCK, 0 ); // 他のUNIXの場合
カウントファイル中のカウント数のデータの書式を次のようにしましょう。 ファイル先頭に数字を表す8桁の文字列を配置します。つまり以下の通りです。
00000000最初にこのような内容のファイルを用意しておきます。
この文字列を読んで整数に変換して、値をひとつ増やす方法は自明です。
データが壊れていて fscanf() でカウント数を読み込めない場合の 対処は各自で決めて下さい。int count; if( 1 != fscanf( fptr, "%d", &count ) ){ exit(1); } count++;
新しいカウント数を元のファイルに書き込みます。その前に書き込み位置を 元の場所に戻す操作が必要です。
rewind()関数はあまり知られていませんが、fseek( fptr, 0, SEEK_SET ) と 同じ意味です。rewind( fptr ); fprintf( fptr, "%08d", count );
排他的ロックを掛けているので、これを解除してからファイルを閉じます。 実は閉じるだけでロックも解除されるので明示的なロック解除は要らないのですが いちおう明示的に解除しましょう。
lockf( fileno(fptr), F_ULOCK, 0 ); flock( fileno(fptr), LOCK_UN ); // FreeBSD only? fclose(fptr);
高水準ファイル入出力でカウント数を得て更新する関数 IncCount() は次のようになるでしょう。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> int IncCount( char* countfile ) { int count; FILE* fptr; if( (fptr=fopen( countfile, "r+" )) == NULL ) goto ERROR_BLOCK; lockf( fileno(fptr), F_LOCK, 0 ); if( fscanf( fptr, "%d", &count ) != 1 ) goto ERROR_BLOCK; count++; rewind( fptr ); fprintf( fptr, "%08d", count ); lockf( fileno(fptr), F_ULOCK, 0 ); fclose(fptr); return count; ERROR_BLOCK: if( fptr != NULL ) fclose(fptr); return 0; }
つまり例えば、「あなたは00012345番目の訪問者です。」のように テキスト文字としてカウント数をWebpageに入れ込む方法です。 カウンタCGIを次のように作ります。名前は ssitextcount.cgi としましょう。
// ssitextcount.cgi int main(void) { printf( "Content-Type: text/plain\n" "\n" "%08d", IncCount("count.dat") ); return 0; }
そしてカウンタを設置したいwebpageでSSIが使えるように 設定しておいて、例えば次のように記述します。
あなたは <!--#exec cgi="ssitextcount.cgi" --> 番目の訪問者です。
つまり例えばwebpage上に 「あなたは<IMG SRC="0.gif"><IMG SRC="1.gif"><IMG SRC="2.gif"> 番目の訪問者です。」のようにカウント数を表す数字のアイコン画像を IMGタグで並べる方法です。
まず、もちろん数字のアイコン画像をどこかから入手して下さい。 私がもっている最も貧弱な数字アイコンたちで良ければこれを利用して下さい。
カウンタCGIを次のように作ります。名前は ssiimgcount.cgi としましょう。
// ssiimgcount.cgi int main(void) { puts("Content-Type: text/html\n"); char num[9]; sprintf( num, "%08d", IncCount("count.dat") ); for( int n=0; n<8; n++ ){ printf("<IMG WIDTH=8 HEIGHT=12 SRC=\"%c.gif\">", num[n] ); } return 0; }
SSIの記述は先の例と同じです。
あなたは <!--#exec cgi="ssiimgcount.cgi" --> 番目の訪問者です。
IMGタグのSRCにCGIを指定して、CGIに画像を出力させながら カウントを取らせる方法です。 その時のカウントの値を一枚のGIF画像として作成するのはちょっと難しいので、 まず単に四角いアイコン を 表示するだけのカウンタの作り方を紹介します。
このアイコンのファイルの内容をCGIが読んで、標準出力に出力するだけでも 良いのですが、ちょっと趣向を変えて、アイコンのファイルの内容を CGIプログラムの変数の中に埋め込んでみましょう。 カウンタCGIを次のように作ります。名前は iconcount.cgi としましょう。
// iconcount.cgi static char icon[] = { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x0c, 0x00, 0x0c, 0x00, 0xf2, 0x00, 0x00, 0xbf, 0xbf, 0xbf, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0x87, 0x94, 0xff, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xb3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x03, 0x25, 0x18, 0xba, 0x1c, 0x12, 0x23, 0x4a, 0x29, 0x08, 0x14, 0x38, 0xe3, 0x62, 0x87, 0xd6, 0xdc, 0xf5, 0x6d, 0xdd, 0x48, 0x8a, 0x63, 0xe8, 0x99, 0xaa, 0x29, 0xb4, 0xac, 0xf5, 0x16, 0x74, 0x4d, 0x13, 0x15, 0xa1, 0xef, 0x7c, 0x02, 0x00, 0x3b }; int main(void) { puts("Content-Type: image/gif\n"); IncCount("count.dat"); fwrite( icon, sizeof(icon), 1, stdout ); return 0; }
Webpageにカウンタを設置する方法はもちろん 「<IMG WIDTH=12 HEIGHT=12 SRC="iconcount.cgi">」です。 閲覧者には表向きにはカウンタであることはわかりません。
カウント値を表す数字列を一枚のGIF画像として表示するカウンタを作ります。 Oから9までの絵柄をPPM形式でプログラム中に用意しておいて、それを カウント数に応じて連結して、ppmtogifコマンドにパイプしてGIF画像を 出力するようにします。
PPMデータを簡単にするため最大輝度を表す値を1にします。 「0」の数字を表す横8ドット、縦12ドットのアイコンのPPM画像データ を配列変数に格納すると以下のようになります。
1がある所が緑に明るく光る所です。 このようなデータを0から9まで用意します。3次元配列変数 image[10][12][8*3]にそれらを格納しておくことにしましょう。static char image0[12][8*3] = { {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0}, {0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0}, {0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0}, {0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0}, {0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0}, {0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0}, {0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0}, {0,0,0,0,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, };
PPMの連結は簡単なので、カウントCGIは次のように簡潔にまとまります。
#define PATH_OF_PPMTOGIF "/usr/X11R6/bin/ppmtogif" int main(void) { close(2); FILE* pipe; if( (pipe=popen( PATH_OF_PPMTOGIF, "w" )) == NULL ) exit(1); char narray[9]; sprintf( narray, "%08d", IncCount("count.dat") ); for( int n=0 ; n<8 ; n++ ){ narray[n] -= '0'; } puts("Content-Type: image/gif\n"); fputs( "P6\n64 12\n1\n", pipe ); for( int i=0 ; i<12 ; i++ ){ for( int n=0 ; n<8 ; n++ ){ fwrite( &image[narray[n]][i], 8*3, 1, pipe ); } } fclose(pipe); return 0; }
カウント値をsprintf()関数で一旦文字列にして、 各文字のASCIIコードをこのように一桁の数字に変換します。 これで image[narray[n]] でカウント値の左から n桁目の数字のPPMデータへの アドレスになります。
数字のPPMデータならびにこのカウンタCGIのソース を置いておきますのでご利用下さい。なおこのソースでは popen()の代わりに pipe(), fork(), dup2(), execl(), waitpid() 等などの システムコール関数を使っています。
Webpageにカウンタを設置する方法はもちろん
「<IMG WIDTH=64 HEIGHT=12 SRC="count.cgi">」です。
絵が小さすぎると思われる人は
「<IMG WIDTH=128 HEIGHT=24 SRC="count.cgi">」としてください。