Perl/CGI研究室 'PERL-LABO'

Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

クッキー重複チェック処理の変更とライブラリ化

研究内容

クッキーによる重複チェックを前回のアクセスから12時間としていましたが、 そのやり方がちょっと間違っていました…。 修正します。それに、アクセスをカウントする部分をライブラリにします。

詳細

すいませんバグってました…

クッキーによる重複回避の部分、バグってました。バグというのはプログラムの不具合、間違いのことです。 バグってるというのは間違ってるということです。 問題の部分は次のところです。

sub checkvisitbycookie
{
	local $visit;
	%cookie = plab::getcookie();
	if ($cookie{'VISIT'} == 1) { $visit = 1; }
	else                       { $visit = 0; }
	$cookie{'VISIT'} = 1;
	plab::setcookie(12, %cookie);	# 有効期限は12時間
	return $visit;
}

VISITという名前のクッキーに1という値が入っていれば、 前回の訪問から12時間経っていないという判断をしています。 12時間という時間間隔はクッキーの有効期限を利用して設定しています。 12時間経てばクッキーが消えるので、VISITという名前のクッキー、 つまり前回の訪問記録も消えるっていうつもりで作りました。

間違っているのは、最初のアクセスから10時間後に訪問したとして、 それが重複アクセスとしてカウントされないっていうところまではいいのですが、 そのとき再び有効期限12時間のクッキーを書き込むため、 その10時間後にまた訪れたときも重複アクセスと判定されてカウントされません。 10時間おきに訪問してくれる方がいたら、その方のアクセスは最初の1回以降は カウントされないわけです。 実際はそのように頻繁にアクセスがあることは大きなサイトでも滅多にないと思いますし それほど大きな誤差は生みませんが、これはちょっとおかしいです。

直接的には、前回のアクセスから〜時間以内のアクセスは重複アクセスとしてカウントしない、 っていうのが間違っているわけですね。前回カウントしてから〜時間以内のアクセスは カウントしないっていうのが正しいです。

ということで修正します。 普通なら前回の記事を修正すべきですが、 研究録ですので不具合修正まで赤裸々に綴ります…

結果

修正方針

さて、重複チェックの処理を修正するんですが、方針としては 「前回 "カウント" してから〜時間以内のアクセスはカウントしない」です。 …でも、どうせ修正するのなら、ちょっと違うやり方を、と思います。 というのは、前回、日毎アクセスの履歴を保存する機能を使いしたときに、ちょっと思ったことがありました。

日毎アクセスを記録する、その区切りは午前0時です。 ですから、クッキーによる重複カウントの回避も、 午前0時を区切りとするのが妥当ではないか?ということです。 前回のアクセスから〜時間経っていなければ云々、という経過時間に基づいたチェックの仕方より、 この方がいいんじゃないかと思います。 前回の訪問と日付を比べて、 同じ日付なら重複アクセスとしてカウントしないようにする。 日付が変わっていたらカウントします。 こうすると、正しく、1日1回だけカウントされるっていうようになります。 朝にアクセスして、同じ日の夜にまたアクセスした場合それはカウントされないことになりますが、 アクセス数=訪問者数という意味で、日毎のアクセスが、1日に訪問してくれた人の数という意味に なるだけで、おかしくはないですよね。

ということで、修正の方針はこのような感じで進めたいと思います。 それと、カウントする部分を例によってライブラリにして再利用しやすくしておきます。

作成したCGIプログラム

counter.cgi
#!/usr/bin/perl

require 'counter.pl';

# カウンタを1増やす
# 引数は、
#    カウンタファイル名、
#    カウンタ履歴ファイル名、
#    IP重複チェックを行う、
#    クッキー重複チェックを行う、
# という意味です
# 戻り値にカウントの値が配列で与えられます
@ret = plab::incl_counter("count.cgi", "count_day.cgi", 1, 1);

# plab::incl_counter がクッキーを使用するので、
# HTTPヘッダ出力時にクッキー情報を出力します
# 引数の-1は有効期限10年という意味
plab::writecookie(-1);

# ヘッダとカウンタ表示
print "Content-type: text/html\n";
print "\n";
print "<body style=\"margin:0;font-size:15px;\"><tt>";

# 配列の1個目の要素でエラーチェックします
if (@ret[0] == 0) {
	# エラーが起きています
	print "ERROR";	
}
else {
	$txt = sprintf(
		"<small>今日</small> %03d <small>昨日</small> %03d <small>計</small> %04d",
		@ret[1], @ret[2], @ret[3]);
	print $txt;
}
counter.pl
# v 1.00

package plab;

require 'lock.pl';
require 'cookie.pl';
require 'stdplab.pl';

# 使い方
#   クッキーによる重複チェックを行う場合はHTTPヘッダ出力時に
#   plab::writecookie() を呼び出すこと

# カウンタ
# 引数(カウントファイル名, 日毎カウントを保存するならファイル名(しないなら空),
#       IPチェックするか否か, クッキーチェックするか否か)
# 戻値は配列で(成功なら1 失敗なら0, 今日カウント, 昨日カウント, 計カウント)
sub incl_counter
{
	local $countfname    = $_[0];
	local $counthisfname = $_[1];
	local $ipcheck       = $_[2];
	local $cookiecheck   = $_[3];

	local($visit, $ip, $lockdir, $line, $day);
	local($totalcount, $todaycount, $yestcount, $prevday, $previp);
	local @ret;

	# クッキーで重複チェック。重複なら$visit が 1 になります。
	$visit = 0;
	if ($cookiecheck) {
		local($prevvisit, $day);
		readcookie();
		$prevvisit = $Cookie{'VISITDATE'};
		$day = getcurrentdatestring();
		if ($prevvisit eq $day) { $visit = 1; }
		$Cookie{'VISITDATE'} = $day;
	}

	# ファイル名など
	$ip        = $ENV{'REMOTE_ADDR'};
	$lockdir   = $countfname . ".lockdir";

	# ロック
	if (! plab::lock($lockdir)) {
		$ret[0] = 0;
		return @ret;
	}

	# 読み込み
	open FILE, "< $countfname";
	$line = <FILE>;
	close FILE;

	($totalcount, $todaycount, $yestcount, $prevday, $previp) = split('<>', $line);

	# 前回と今回でIPアドレスが異なっていて、かつ、
	# クッキーに前回訪問の記録が残っていなかったらカウントします
	if ((! $ipcheck || $previp ne $ip) && ! $visit)
	{
		$day = getcurrentdatestring();
		if ($prevday ne "" && $prevday ne $day) {
			# 日付が変わりました
			# 昨日のアクセス数を別ファイルに移し日付カウントをリセットします
			if ($counthisfname ne "") {
				if (! open(DAYFILE, ">> $counthisfname")) {
					plab::unlock($lockdir);
					$ret[0] = 0;
					return @ret;
				}
			}
			print DAYFILE "$prevday<>$todaycount\n";
			close DAYFILE;
			$yestcount  = $todaycount;
			$todaycount = 0;
		}

		++$totalcount;
		++$todaycount;
	
		if (! open(FILE, "> $countfname")) {
			plab::unlock($lockdir);
			$ret[0] = 0;
			return @ret;
		}

		print FILE "$totalcount<>$todaycount<>$yestcount<>$day<>$ip";
		close FILE;
	}

	plab::unlock($lockdir);

	@ret = (1, $todaycount, $yestcount, $totalcount);
	return @ret;
}

1;
stdplab.pl
# v 1.00

package plab;

# 現在の日付を文字列で作る
sub getcurrentdatestring
{
	local(@t, $day);
	@t = localtime(time());
	$day = sprintf("%04d/%02d/%02d", $t[5]+1900, $t[4]+1, $t[3]);
	return $day;
}

1;

実行結果

カウンターです。昨日、今日、計のアクセス数を表示しています。

次のようなIFRAMEタグで表示しています。

<iframe src=counter.cgi width=210 height=20 frameborder=0></iframe>

解説

プログラムの解説です

コードの量が多いので、もし以下の記事を読んでくださるなら もう1つウインドウを開いてもらえると 見やすいかも知れません。

counter.cgi

CGIプログラム本体です。コメントをたくさん入れたので長いようですが、 基本的にカウントを増やす関数とクッキーを出力する関数を呼んで、 HTMLを出力しているだけでシンプルです。コメントのせいで余計ごちゃごちゃしたかな。

カウントを増やす関数は別ファイルにあります。 counter.pl の plab::incl_counter です。 引数に、カウントファイル名、カウントの履歴を保存するファイル名、 IPアドレスを使った重複チェックをするかどうか、 クッキーを使った重複チェックをするかどうか、の4つを与えるようにしています。 重複チェックは常に行うようにしてもいいと思いますが、結構微妙で、 このアクセスカウント関数、使い方によっては、 重複チェックをせずにカウントしたい場合というのもありえるように思うので そうしています。

戻り値に現在のカウントを返しますが、今日と昨日と計があるので配列で返しています。 それに、エラー発生の有無も配列に入れて返しています。

その後、HTTPヘッダ出力時にクッキーを plab::writecookie 関数で出力します。 クッキーによる重複チェックをしないならこれは必要ありません。

で、HTMLを出力します。これは前と同じですが、 戻り値の配列の1個目がエラーの有無、その後ろに3つカウンタの数値が入っているので それを使って処理しています。

本体はこれだけです。かなりシンプルです。クッキーの出力がHTTPヘッダの前に 必要というのを忘れないようにしないといけないのがネックですが、それ以外はいい感じです。

ライブラリ化したとき嬉しいことの1つは、本体がすっきりすることですね。 本体がすっきりしてるとなんか気持ちがいいです。

counter.pl

カウンタ本体です。 1行名に、# v 1.00 という形でバージョンを入れています。 counter.pl はまだ最初なのでバージョン管理とかいらないですが、 今後のために、バージョンを付けるクセを付けようと思って付けました。

カウントする関数は incl_counter。 そういえば、incl というのはインクリメント(1つ増やす)の略のつもりです。 関数の引数は上に書いたようなものです。

変わったのはクッキーの処理のところです。 本題である、クッキーに日付を記録しておいて、 その日付で重複チェックを行うっていう処理にしています。 これは日毎アクセスを保存する研究をしたときと同じやり方なので簡単でした。 それに、クッキー処理ライブラリの仕様を変えたので、 以前の getcookie 関数じゃなくて readcookie 関数になって、 plab::Cookie を使ってクッキーにアクセスするっていう形になっています。

stdplab.pl

新しく作ったファイルです。 getcurrentdatestring という関数が1つだけあります。 この関数、現在の日付を YYYY/MM/DD という形式の文字列にして返すんですが、 なんか便利かも知れないな?と思ってライブラリにしました。 ファイル名は stdplab.pl としていますが、std というのはスタンダード、標準、という意味で、 標準plabライブラリ、です (^^ まぁ、どのライブラリに入れたらいいのか分からないような 関数を入れておくファイルっていう感じです。

動きました

ふぅ。 重複チェックのやり方を変更するっていうのはそれほど難しい変更ではありませんでしたが、 クッキー処理ライブラリの仕様変更があったのでちょっと大変でした。 でもこれで、アクセス数カウントも関数を1つ呼び出すだけになり、 また1つ役に立ちそうなライブラリが増えました!

Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

Copyright (c) 'PERL-LABO' All Rights Reserved.  リンクフリーです。