[←Back]   4. カウンター作りに挑戦しよう  [Next→]

   4-1 カウンターの基本は?
カウンターの基本は、「3-4 ファイル入出力」でやった通りです。
一応、これでアップロードすればカウンターとして機能します。…が、これじゃちょっと寂しいですね。
これをもう少し改造して、機能をつけましょう。ただ、これから説明しやすいようにプログラムを少し変えます。

#!/usr/local/bin/perl
open(FILE, "+<count.log");
$count = <FILE>;
$count++;
seek( FILE, 0, 0 );
print(FILE "$count \n");
close(FILE);

print "Content-type: text/html\n\n";
print "<HTML>";
print $count;
print "</HTML>";
exit;
どこを変えたかというと、ファイルを読み書きモードでオープンしています。
前回は読み込みと書き込みで2回ファイルを開いたけど、こうすれば1回で済みます。

ただしこの場合、書き込む前に
 seek( FILE, 0, 0 );
をつけて、ファイルの先頭を指してあげます。これを忘れると、上書きしてくれません。


   4-2 ファイルのロック
上のままでは、複数の人が同時に入ってきた時にうまくカウントしてくれません。
ちゃんと番号をカウントアップするためには、書き込みの時にファイルをロックする必要があります。
下がそのサンプルです。追加した部分は赤色で示してあります。

#!/usr/local/bin/perl
open(FILE, "+<count.log");
flock( FILE, 2 );
$count = <FILE>;
$count++;
seek( FILE, 0, 0 );
print(FILE "$x \n");
close(FILE);
flock( FILE, 8 );

print "Content-type: text/html\n\n";
print "<HTML>";
print $count;
print "</HTML>";
exit;
じゃぁ説明。ファイルのロックにはflockを使います。使い方は下の通りです。
 ・flock( FILE, 1 );  読み込み宣言ロック
 ・flock( FILE, 2 );  書き込み宣言ロック
 ・flock( FILE, 8 );  ロック解除
ただし、これはWin + AN Httpdの環境ではエラーが出てしまいました。
どうやら、Win(NTは除く)およびMacでは使えないみたいですが…すいません、まだ詳しくは分かりません。m(_ _)m


   4-3 SSIを使って表示しよう
カウンターは、普通HTMLに組み込んで使います。 …が、このままではCGIにアクセスしないとカウンターとして機能しませんね。

そこで使うのがSSIです。SSIは、HTMLにCGIの実行結果を埋め込むときに使います。
SSIには「#include方式」「#exec方式」という2つの方式がありますが、ここでは「#include方式」を使います。

この方式では、HTMLのカウンターを表示したい位置に
 <!--#include virtual="CGIファイル名" -->
と書きます。するとサーバはそのCGIを実行し、その結果を置き換えてくれます。

▼ count.cgi ▼
#!/usr/local/bin/perl
open(FILE, "+<count.log");
$x = <FILE>;
$x++;
seek( FILE, 0, 0 );
print(FILE "$x \n");
close(FILE);

print "Content-type: text/html\n\n";
print $x;
exit;
このように、SSIを使う場合でもCGIファイルは特に変更ありません。
ただSSIで使うということは、<HTML>、</HTML>はすでに存在しているので削除しておきましょう。

▼ index.shtml ▼
<HTML>
<BODY>
あなたは
<!--#include virtual="cgi-bin/count.cgi" -->
人目の訪問者です。
</BODY>
</HTML>
次に、HTMLファイルは上のような感じです。
SSIが使えるファイルの拡張子を何にするかはWebサーバ側の設定になります。
一般にxxx.htmlの他にxxx.shtmlがあります。


   4-4 gif画像を使おう
「テキストのカウンターじゃ寂しい!」なんて人もいるでしょう。 というわけで、表示にgif画像を使ってみましょう。

まず、1から9の画像を用意して、「1.gif、2.gif…」のように名前を付けます。
それを、CGIと同じフォルダに放り込んどきましょう。次に、CGIファイルは下のような感じです。

#!/usr/local/bin/perl

open( FILE, "+< count.log" );
$count = <FILE>;
$count++;
seek( FILE, 0, 0 );
print FILE "$count\n";
close( FILE );

print "Content-type:text/html\n\n";
$out = sprintf($count);
$out =~ s/(.)/<IMG src=".\/$1.gif">/g;
print $out;

exit;
$out = sprintf($count);
では、カウントを「$out」という変数に保管します。「sprintf」は文字列を返却($outに設定)するという意味です。

$out =~ s/( . )/<IMG src=".\/$1.gif">/g;
の説明は後回しにします。ってか、自分でも理解してない…(汗);


   4-5 Today、Yesterdayを表示しよう
ネットサーフィンをしていると、よく総カウントの他に今日と昨日のカウントを 表示しているカウンターがありますよね。
実はこのカウンター、3-5で出てきたsplitを使えば作れちゃうんです♪
さっそく行ってみましょ〜 (Kentさんのdaycountを参考にしています)

#!/usr/local/bin/perl

$logfile = "count.log";
open(IN,"$logfile");
$data = <IN>;
close(IN);

($count,$today,$yesterday,$day) = split(/:/, $data);
$count++;
$ENV{'TZ'} = "JST-9";
($sec,$min,$hour,$mday,$mon,$year) = localtime(time);
   if ($day eq "$mday"){ $today++; }
   else {
   $yesterday = $today;
   $today = 1;
   }

$newdata = "$count:$today:$yesterday:$mday";
open(OUT,">$logfile");
print OUT $newdata;
close(OUT);

print "Content-type: text/html\n\n";
print "あなたは$count人目の訪問者です<BR>";
print "yesterday:$yesterday &nbsp; today:$today";
exit;
($count,$today,$yesterday,$day) = split(/:/, $data);
では、データを(カウント、今日のカウント、昨日のカウント、日付)に分解しています。
ここで、$dayは「前回のカウントの時の日付」を指しているのがポイントです。

if ($day eq "$mday") { $today++; }
前回と今回の日付が同じ時には、Todayのカウントを1アップさせます。

else { $yesterday = $today; $today = 1; }
そうでない場合は、日付が変わったということなので、Todayは昨日のカウントになります。
そして、代わりにToday = 1と設定してあげましょう♪


   4-6 IPチェックをしよう
例えば、トップページとメインページにカウンターを置いたとします。
すると、トップページで1カウント、メインページでも1カウント刻むことになってしまいます。

そこで、同じIPアドレスが連続した場合にはカウントしないように設定しましょう。

#!/usr/local/bin/perl

$logfile = "count.log";
open(IN,"$logfile");
$data = <IN>;
close(IN);
($count,$ip) = split(/:/, $data);

$addr = $ENV{'REMOTE_ADDR'};
if ($addr ne "$ip") { $count++; }

$newdata = "$count:$addr";
open(OUT,">$logfile");
print OUT $newdata;
close(OUT);

print "Content-type: text/html\n\n";
print "あなたは$count人目の訪問者です";
exit;
プログラム中の$addr = $ENV { 'REMOTE_ADDR' } ; の部分で、IPアドレスを取得しています。
他にも$ENV{ 環境変数名 }を使えば、いろいろな情報を入手できます。

環境変数名説明
REQUEST_METHOD リクエストメソッド(GET・POST等)
QUERY_STRING CGIに送信される情報
PATH_INFO パス情報
REMOTE_HOST リモート(ブラウザ)ホスト名
REMOTE_ADDR リモート(ブラウザ)IPアドレス
REMOTE_USER リモート(ブラウザ)ユーザ名
HTTP_REFERER CGIが呼び出される前に参照していたページのURL
CONTENT_TYPE CGIに送信される情報のMIMEタイプ
CONTENT_LENGTH CGIに送信される情報の長さ(バイト数)

例えばリモートホスト名を記録しておけば、どのカウントを誰が踏んだか、ある程度分かります。
また、CGIが呼び出される前に参照していたページのURLを記録しておけば、自分のページがどこからリンクを張られているか分かっちゃいます。


   4-7 まとめると…
「4−2」〜「4−6」の機能を1つにまとめると、下のようになります。
よくあるフリーのCGIのように、設定は全部手前に持ってきました。

▼ count.cgi ▼
#!/usr/local/bin/perl

# ---設定---
# ログファイルの名前
$logfile = "count2.log";

# gif画像がおいてあるフォルダ(index.shtmlからのパス)
$gif = "cgi-bin/count/gif";

# ファイルのロック(0:しない 1:する)
$lock = 0;

# 総カウントの画像表示(0:しない 1:する)
$gif_count = 1;

# IPチェック(0:しない 1:する)
$ip_count = 0;

# Today、Yesterdayの表示(0:しない 1:する)
$day_count = 1;

# ---プログラム本体---
open(IN,"$logfile");
$data = <IN>;
close(IN);

# データおよび環境変数の読み込み
($count,$today,$yesterday,$day,$ip) = split(/:/, $data);
$addr = $ENV{'REMOTE_ADDR'};
$ENV{'TZ'} = "JST-9";
($sec,$min,$hour,$mday,$mon,$year) = localtime(time);

# ip_count=0またはIPが違うならカウントアップ
if (!$ip_count || $addr ne "$ip"){
   $count++;
   if ($day eq "$mday"){ $today++; }
   else {
   $yesterday = $today;
   $today = 1;
   }
}

# 新しい情報の書き込み
$newdata = "$count:$today:$yesterday:$mday:$addr";
open(OUT,">$logfile");
print OUT $newdata;
close(OUT);

# HTMLに出力
print "Content-type: text/html\n\n";
   if ($gif_count){
      $out = sprintf($count);
      $out =~ s/(.)/<IMG src="$gif\/$1.gif">/g;
      print "あなたは$out人目の訪問者です<BR>\n";
   }
   else {print "あなたは$count人目の訪問者です<BR>";}
if ($day_count) {print "yesterday:$yesterday &nbsp; today:$today";}
exit;


▼ index.shtml ▼
<HTML>
<HEAD>
<TITLE>かうんたぁ</TITLE>
</HEAD>

<BODY>
<CENTER>
<FONT SIZE="5">カウンター</FONT>
<!-- #include virtual="cgi-bin/count/count.cgi" -->
</CENTER> </BODY> </HTML>
気が付いた人もいると思いますが、ファイルのロックがありません(笑)
上にも書いたように、flockが最もロックファイルに適しているのですが、環境によっては使えないという問題があるからです。 そこで、フリーで配布されているCGIでは、他のロック処理が使われています。(その種類についてはまた・・・)



[←Back]   BACK   [Next→]