中井技術工房 CGI アプローチ トップ > CGIアプローチ > ここ

実際に採用したアクセスカウンタ
採用したカウンタCGI

●とほほさんのフリーソフトを利用

次の CGI を利用させていただきました。難解なコーディングですが、非常によくできており、簡単に自分用に調整できました。
からくりはある程度理解しましたので、コメント付きソースをこの頁の下の方で表示しています。

とほほのCGIソフト集(http://www.tohoho-web.com/wwwsoft.htm)

この中の「WwwCounter」をダウンロードすればOKです。先頭部分だけをいじりました。

●素材(カウンタ画像) gif 画像は次のところの下位頁の「フリー素材/アクセスカウンタ」からダウンロードさせていただきました。

 超初心者のホームページ作成(http://beginners.atompro.net/htm_free.html)
 

●修正したところ

●できばえ

当URL の先頭に表示されているカウンタです。
実は隠しカウンタも設置していますが、こちらの方が有効です。

●ソースの問題点

   
●ファイル格納状態


    ----+- www (publc_html 相当) +-
                                 |
                                 +- CounterHead -+- lock ( 排他制御用ディレクトリ、作った)
                                                 |
                                                 +- wwwcount.htm (テストプログラム、後述) 
                                                 |
                                                 +- wwwcount.cgi ( CGI 本体)
                                                 |
                                                 +- wwwcount.acc (アクセスログ)
                                                 |
                                                 +- wwwcount.cnt (カウンター値)
                                                 |
                                                 +- wwwcount.dat (日付情報)
                                                 |
                                                 +- 0〜9.gif     ( 0〜9 の数字の gif ファイル )
さくらインターネットでは www というディレクトリがホーム(public_html 相当)になっていますので、その下に CounterHead というディレクトリを作りました。

●パーミッション設定

「使用準備」の頁を参照ください。非常に重要です。
最初、この設定をいいかげんにして、どこにもまちがいが見つからないのに正常に動作せず、さんざん悩みましたので。
 
●カウンタ 呼び出し html

index.html の適当な位置に次の1行を入れただけです。

   <img src="CounterHead/wwwcount.cgi?gif" width=96 height=18 alt="Counter">


●コメント付きCGIソース

とほほさんのソースに理解したコメントを追加しています。



    #!/usr/bin/perl
    
    #==================================================================
    # 次のところから拝借 Dec.31,2014
    # http://tohoho.wakusei.ne.jp/wwwsoft.htm
    #==================================================================
    
    #==================================================================
    # 使いかた:
    #==================================================================
    #   (書式1) wwwcount.cgi?test
    #	CGIが使用できるかテストを行う。
    #
    #   (書式2) wwwcount.cgi?text
    #	カウントアップを行い、カウンタをテキストで表示する。
    #
    #   (書式3) wwwcount.cgi?gif
    #	カウントアップを行い、カウンタをGIFで表示する。
    #
    #   (書式4) wwwcount.cgi?hide+xxx.gif
    #	カウントアップを行い、xxx.gifを表示する。
    
    #==================================================================
    # カスタマイズ:
    #==================================================================
    
    
    # ★ Windows NT で IIS を使用する場合は、wwwcount.cgi がインストー
    #    ルされているフォルダ名を 'C:/HomePage/cgi-bin' などのように指
    #    定してください。(必須)
    $chdir = '';
    
    # ★ SSIのテキストモードで使用する場合は、$mode = "text"; としてく
    #    ださい。(必須)
    $mode = "";
    
    # ★ CGIとしては動いているのに、wwwcount.cgi?test でテストできない
    #    場合、下記の1行の先頭の # を削除してみてください。
    #@ARGV = split(/\+/, $ENV{'QUERY_STRING'});
    
    # ★ 表示桁数を例えば5桁に指定する場合は「$figure = 5;」のように指
    #    定してください。0 を指定すると桁数自動調整になります。
    $figure = 5;
    
    # ★ ファイルロック機能をオンにする場合は 1 を、オフにする場合は 0
    #    を指定してください。通常は 1 でよいでしょう。
    $do_file_lock = 1;
    
    # ★ 同アドレスチェック機能をオンにする場合は 1 を指定してください。
    #    同じ日に同じ IP アドレスからのアクセスをカウントアップしなく
    #    なります。
    $do_address_check = 1;
    
    # ★ レポート機能を使う場合は「$mailto = 'abc@xxx.yyy.zzz';」のよう
    #    に自分のメールアドレスを設定してください。サーバーで sendmail
    #    コマンドがサポートされている必要があります。
    $mailto  = 'nakai99@sand.ocn.ne.jp';
    
    # ★ レポート機能の送信元メールアドレス(通常は自分のアドレス)を
    #    指定してください。省略時はカウンタ名になりますが、プロバイダ
    #    によっては、送信元メールアドレスが適切なものかチェックしてい
    #    るケースがあります。
    $mailfrom = 'nakai99@sand.ocn.ne.jp';
    
    # ★ レポート機能で、sendmail コマンドのパス名が /usr/lib/sendmail
    #    と異なる場合は、適切に修正してください。
    $sendmail = '/usr/sbin/sendmail';
    
    # ★ レポート機能で、詳細情報を添付せず、アクセス件数のみをレポー
    #    トする場合は、0 を指定してください。
    $account_detail = 1;
    
    # ★ レポート機能で、アクセス元のホスト名を取得できない場合に、は、
    #    この値を 1 に変更すると、IPアドレスからホスト名への変換を試み
    #    るようになります。ホスト名変換は、サーバー負荷の原因にもなるの
    #    でご注意ください。
    $do_addr_to_host = 0;
    
    # ★ レポート機能において、「$my_url = 'http://www.yyy.zzz/';」とす
    #    ると、このアドレスにマッチするサイトからの FROM は表示しなくな
    #    ります。
    $my_url = '';
    
    # ★ レポート機能で、%7E などのエンコード文字をデコードして記録する
    #    場合は 1 を、そのまま記録する場合は 0 を指定してください。
    $do_decode_url = 0;
    
    # ★ 省略時のカウンター名を指定します。カウンター名は *.cnt や *.dat
    #    などのファイル名に対応しています。
    $count_name = "wwwcount";
    
    #==================================================================
    # 処理部:
    #==================================================================
    
    #
    # カレントフォルダを変更する。
    # ネット上で使うときは指定しない
    if ($chdir ne "") {
    	chdir($chdir);
    }
    
    #
    # ●関連するファイルを洗い出しておく
    #   指定ファイル名に固定の拡張子を追加してファイル名を合成
    $file_count  = "$count_name" . ".cnt";        # カウンタファイル
    $file_date   = "$count_name" . ".dat";        # 日付ファイル、その日一回だけ書替
    $file_access = "$count_name" . ".acc";        # アクセスログファイル
    $file_lock   = "lock/$count_name" . ".loc";   # ロックファイル
    
    #
    # ●引数を解釈する by 環境変数
    #   動作モードの決定
    
       # 引数を +セパレータで分割
    @ARGV = split(/\+/, $ENV{'QUERY_STRING'});
      # 分割文字列ごとに処理
    for ($i = 0; $i <= $#ARGV; $i++) {
    	if ($ARGV[$i] eq "test") {
    		test();   # test() を実行 sub test() で exit() が呼び出されて終了する
    	} elsif ($ARGV[$i] eq "text") {
		$mode = "text";   # テキストモードを指定
	} elsif ($ARGV[$i] eq "gif") {
		$mode = "gif";    # gif モードを指定
	} elsif ($ARGV[$i] eq "hide") {
		$mode = "hide";   # hide モードを指定
		# 1つ後ろの指定文字列を $giffile に取り出す
		# hide+???
		$giffile = $ARGV[++$i];
		# giffile の最後に .gif がくっついていると異常処理
		# ファイル名が指定されているという意味だと思われる
		if (!($giffile =~ /\.gif$/i)) {
			exit(1);
		}
		# giffile に <> や & が含まれていると異常終了
		# 意図不明だが、普通に使うとここへはこない
		if ($giffile =~ /[<>|&]/) {
			exit(1);
		}
	} elsif ($ARGV[$i] eq "name") {
                # name 指定があればファイル名を書替え
                # この使い方の説明がヘッダ部にないが?
                # name+???
		$count_name = $ARGV[++$i];
		   # 先頭がアルファベット、数字以外なら異常終了
		   # +$ は何?
		if ($count_name !~ /^[a-zA-Z0-9]+$/) {
			exit(1);
		}
		$file_count  = "$count_name" . ".cnt";
		$file_date   = "$count_name" . ".dat";
		$file_access = "$count_name" . ".acc";
	} elsif ($ARGV[$i] eq "ref") {
	        # ref 指定あれば $reffile として保存
	        # この使い方の説明がヘッダ部にないが?
		$reffile = $ARGV[++$i];
	}
    }
    
    #
    # ●環境変数TZを日本時間に設定する
    #
    $ENV{'TZ'} = "JST-9";
    
    #
    # ●ロック権を得る
    # ファイルロック機能オンが指定されているときだけ
    if ($do_file_lock) {
        # 6 回トライするという意味
	foreach $i ( 1, 2, 3, 4, 5, 6 ) {
	        # パーミッション755 で Dir を作る。
                # ちょっとバグなのか意図的なのかは不明だが
	        # $file_lock は上で "lock/????.loc" として合成されているので
	        # テストプログラムで実行してみると 〜/lock/???.loc というディレクトリができる
	    # 動作に問題はないが作られるのはファイルではなくディレクトリであることに注意されたし
		if (mkdir("$file_lock", 0755)) {
			# ロック成功。次の処理へ。
			last;  # 内側のループを抜ける
		} elsif ($i == 1) {
			# 10分以上古いロックファイルは削除してしまう。
			($mtime) = (stat($file_lock))[9];   # 最終更新時刻(=[9])を取得
			if ($mtime < time() - 600) {   # time() は秒数
				rmdir($file_lock);
			}
		} elsif ($i < 6) {
			# ロック失敗。1秒待って再トライ。
			sleep(1);
		} else {
			# 何度やってもロック失敗。あきらめる。
			exit(1);
		}
	}
    }

    # ●ロックファイルクリーンアップ対策
    # 途中で終了してもロックファイルが残らないようにする
    # $SIG は特別な変数シグナルハンドラです。
    # 異常時に O/S が送ってくるようです。関数を定義すればそのシグナルに対応して実行されます。
    # 常にロックディレクトリを削除する。
    sub sigexit { rmdir($file_lock); exit(0); }
    $SIG{'PIPE'} = $SIG{'INT'} = $SIG{'HUP'} = $SIG{'QUIT'} = $SIG{'TERM'} = "sigexit";

    # ●日付判定
    # カウンターファイルからカウンター値を読み出す。
    # 1回前のカウンタを取り出すことになる
    if (open(IN, "< $file_count")) {
	$count = ;
	close(IN);
    } else {
	$count = -1;   # 次のカウントアップで0にするため
    }

    #
    # 日付ファイルから最終アクセス日付を読み出す。
    #
    if (open(IN, "< $file_date")) {
       	$date_log = ;
       	close(IN);
    } else {
	$date_log = "";
    }
    
    #
    # 今日の日付を得る
    # localtime() はデータが若干変則的なので補正が必要になる
    # 秒(0-59),分(0-59),時(0-23),日(1-31),月(0-11),年(1900からの年数)
    ($sec, $min, $hour, $mday, $mon, $year) = localtime(time());
    $date_now = sprintf("%04d/%02d/%02d", 1900 + $year, $mon + 1, $mday);
    $time_now = sprintf("%02d:%02d:%02d", $hour, $min, $sec);
    
    # ●アクセスログのメール送信
    # 日付が異なる、つまり、今日初めてのアクセスであれば
    # アクセスログを読み出してメール送信する
    #
    if ($date_log ne $date_now) {
    
	#
	# アクセスログをメールで送信する
	#
	if ($mailto ne "") {
	        # アクセスファイルを1行ずつ読み出して COUNT 文字列をカウント
	        # ^ は先頭にマッチするかを指示(先頭が COUNT〜の行をカウント)
		$tmp_count = 0;
		open(IN, "< $file_access");
		while () {
			if (/^COUNT/) {
				$tmp_count++;
			}
		}
		close(IN);
		# To,From,Subject 文字列を連結合成して、$msg として格納
		$msg = "";
		$msg .= "To: $mailto\n";
		if ($mailfrom eq "") {
			$msg .= "From: $count_name\n";
		} else {
			$msg .= "From: $mailfrom\n";
		}
		$msg .= "Subject: Head Acs $date_log $tmp_count\n";
		$msg .= "\n";
		
		# 詳細出力フラグ=1ならアクセスログファイルの中味を全部、1行ずつ取り出してさらに文字列連結する
		# 結局1行だけの合成文字列を1回で送信する
		if ($account_detail) {
			open(IN, "< $file_access");
			while () {
				$msg .= $_;
			}
			close(IN);
		} else {
			$msg .= "Access = $tmp_count\n";
		}
		# この連結文字列 $msg をメール出力する
		open(OUT, "| $sendmail $mailto");
		print OUT $msg;
		close(OUT);
	}

	#
	# アクセスログファイルを初期化する
	# 書替へによって空にする
	open(OUT, "> $file_access");
	close(OUT);

	#
	# 今日の日付を日付ログファイルに書き出す
	#
	open(OUT, "> $file_date");
	print(OUT "$date_now");
	close(OUT);
    }
    
    # ●カウントアップ判定 
    # すでに同アドレスからのアクセスがあればカウントアップしない
    # 日々アクセスログは更新するので、その日の重複は無視という処理になる
    $count_up = 1;     # デフォルトはカウントアップする
       # チェックが指示されているときだけ対処
    if ($do_address_check) {
	open(IN, "$file_access");
	while () {
		if ($_ eq "ADDR  = [ $ENV{'REMOTE_ADDR'} ]\n") {
			$count_up = 0;   # チェックアドレスと一致したのでカウントアップしないを指示
			last;
		}
	}
	close(IN);
    }

    #
    # ●カウントアップ処理
    #
    if (($count >= 0) && ($count_up == 1)) {

	#
	# カウンタをひとつインクリメントする
	#
	$count++;

	#
	# アクセスログを記録する
	#
	open(OUT, ">> $file_access");

	# カウント
	print(OUT "COUNT = [ $count ]\n");

	# 時刻
	print(OUT "TIME  = [ $time_now ]\n");

	# IPアドレス
	$addr = $ENV{'REMOTE_ADDR'};
	print(OUT "ADDR  = [ $addr ]\n");

	# ホスト名
	$host = $ENV{'REMOTE_HOST'};
	if ($do_addr_to_host && (($host eq "") || ($host eq $addr))) {
		$host = gethostbyaddr(pack("C4", split(/\./, $addr)), 2);
	}
	if (($host ne "") && ($host ne $addr)) {
		print(OUT "HOST  = [ $host ]\n");
	}

	# エージェント名=ブラウザ情報
	print(OUT "AGENT = [ $ENV{'HTTP_USER_AGENT'} ]\n");

	# リンク元(SSI)
	$referer = $ENV{'HTTP_REFERER'};
	if (($mode eq "text") && ($referer ne "")) {
		if ($do_decode_url eq 1) {
			$referer =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C", hex($1))/eg;
		}
		print(OUT "REFER = [ $referer ]\n");
	}

	# リンク元(CGI)
	$reffile =~ s/\\//g;  # \ を全部削除(アドレス比較に\がじゃまなために除く
	    # reffile が定義されていて
	    # my_url も定義されていて、かつreffile が my_url 指定とことなるときだけ出力
	if ($reffile && (!$my_url || ($reffile !~ /$my_url/))) {
		if ($do_decode_url eq 1) {
			$reffile =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C", hex($1))/eg;
		}
		print(OUT "FROM  = [ $reffile ]\n");
	}

	print(OUT "\n");
	close(OUT);

	#
	# カウンタをカウンタファイルに書き戻す
	#
	if (open(OUT, "> $file_count")) {
		print(OUT "$count");
		close(OUT);
	}
    }

    #
    # ●CGIスクリプトの結果としてカウンターを書き出す
    #
    if ($count == -1) {
	$count = 0;
    }
      # カウンタ数字を文字列変換
    if ($figure != 0) {
         # 桁数指定のとき 巧妙な変換テクだ!
         # 例) $figure=5 なら
         #     sprintf("%05ld",$count); に変換される=上位には必要に応じて0が埋まる
	$cntstr = sprintf(sprintf("%%0%dld", $figure), $count);
    } else { # 桁数指定なしのとき、単にカウンタ数字を文字列変換
	$cntstr = sprintf("%ld", $count);
    }
      # カウンタ文字列を指定モードに応じて出力
    if ($mode eq "text") {
        # text モードなら単にカウンタ数字を文字列出力
	printf("Content-type: text/html\n");
	printf("\n");
	printf("$cntstr\n");
    } elsif ($mode eq "gif") {
        # gif モードならカウンタ桁数に応じた数字gif を連結して出力
	printf("Content-type: image/gif\n");
	printf("\n");
	@files = ();
	    # 最上位桁から順番に1桁ずつ処理
	for ($i = 0; $i < length($cntstr); $i++) {
		$n = substr($cntstr, $i, 1);   # 数字列から1桁分を取り出す
		push(@files, "$n.gif");    # push は配列の最後にデータをくっつける
	}
	require "gifcat.pl";
	binmode(STDOUT);
	print gifcat::gifcat(@files);	# print &gifcat'gifcat(@files); は古い表記のようだ
	                                # by http://www.perl-labo.org/other/gifcat/
	                                # Dec.31,2014 改正
	                                # これは画像ファイル配列を連結している
    } elsif ($mode eq "hide") {
        # hide モードなら+giffile として指定された gif ファイルを表示
	printf("Content-type: image/gif\n");
	printf("\n");
	$size = -s $giffile;
	open(IN, $giffile);
	binmode(IN);
	binmode(STDOUT);
	read(IN, $buf, $size);
	print $buf;
	close(IN);
    }

    #
    # ●ロック権を開放する
    #   /lock/????.loc という「ディレクトリ」を削除する
    if ($do_file_lock) {
	rmdir($file_lock);
    }

    #
    # CGIが使用できるかテストを行う。
    #
    sub test {
	print "Content-type: text/html\n";
	print "\n";
	print "<html>\n";
	print "<head>\n";
    print "<title>Test</title>\n";
    print "</head>\n";
	print "<body>\n";
	print "<p>OK. CGIスクリプトは正常に動いています。</p>\n";
	if ($mailto ne "") {
		if (! -f $sendmail) {
			print "<p>ERROR: $sendmail が存在しません。</p>\n";
		}
	}
	if (-d $file_lock) {
		print "<p>ERROR: $file_lock が残っています。</p>\n";
	}
	if (! -r $file_count) {
		print "<p>ERROR: $file_count が存在しません。</p>\n";
	} elsif (! -w $file_count) {
		print "<p>ERROR: $file_count が書き込み可能ではありません。</p>\n";
	}
	if (! -r $file_date) {
		print "<p>ERROR: $file_date が存在しません。</p>\n";
	} elsif (! -w $file_date) {
		print "<p>ERROR: $file_date が書き込み可能ではありません。</p>\n";
	}
	if (! -r $file_access) {
		print "<p>ERROR: $file_access が存在しません。</p>\n";
	} elsif (! -w $file_access) {
		print "<p>ERROR: $file_access が書き込み可能ではありません。</p>\n";
	}
	if (($chdir ne "") && (! -d $chdir)) {
		print "<p>ERROR: $chdir が存在しません。</p>\n";
	}
	print "</body>\n";
	print "</html>\n";
	exit(0);
    }

 


トップ > CGIアプローチ トップ > 頁トップ