|目次|手順 0|1|2|3|A|B|C|D|E|F|G|H| I |J|K|L|M|N|O|P|Q|
作成日:2001年01月26日、更新日:2003年02月17日 作成:鷹の巣私のWebページで使用しているKENT WEBさんのアクセスカウンタとアクセスレポートの改造例をご紹介します。
目次
カウンタの方式ですが、トップページだけは、SSIを許可してSSI式のテキストカウンタを使用されることを推奨します。自宅サーバーの場合は、第三者にWebスペースを貸さないことが前提です。画像を使用したグラフィックカウンタに比べて、テキストカウンタは、美しくないですが、代わりに応答性がよくなります。テキストカウンタを設置されましたら、今まで美しさと引き換えに著しく応答速度が犠牲になっていたことを体感できることと思います。
私の場合は、総合カウント数と本日のカウント数と昨日のカウント数の3個のカウンタを使用していますが、Webサーバーから一回の呼び出しで、カウント数が表示されます。ただし、SSIを許可するとWebサーバーは、htmlファイル中にSSI命令が書かれていないかを全て調べますから、htmlファイルのファイル容量を小さくする必要があります。また、SSIを使用しますので、掲示板などのCGIは、タグの書き込みを受け付けない様になっていることが前提となります。
テキストカウンタでも以下の様にHTMLタグで書ける範囲で、ある程度、綺麗なものを作ることができます。
2001年06月01日より貴方は、 376271 人目(本日:864人、昨日:1187人)のご訪問者です。(すっきり表示しています。)
2001年06月01日より貴方は、 1234 人目(本日:12人、昨日:123人)のご訪問者です。(多少、すっきり表示しています。)
2001年06月01日より貴方は、 01234 人目( 本日: 012 人、 昨日: 123 人)のご訪問者です。(グラフィックカウンタに近くなりました。)
どんなブラウザからも見ることができるようにフォントを選ぶ必要がありますが、工夫次第ではグラフィックカウンタに近いものにすることができます。
私のWebページは、フレームを使用していません。カウンタを設置しているホームページ(トップページ)より、各ページに行って再びホームページに戻ってくる間に、他のIPアドレスのご訪問者がホームページにやってくるとカウンタが多くカウントされるようになります。これは、カウンタが一つ前のIPアドレスしか記憶していないため、異なったIPアドレスが交互にホームページにやってくると毎回カウントするからです。従って、カウンタが訪問者数を表示するのではなく、ホームページが何回見られたかを表示するようになります。
これを防止する方法は色々ありますが、このだけのためにクッキーを発行するというのは、利用者の便宜を損ねます。従って、サーバー側で、複数IPアドレスを蓄積するように改造しました。
参考:質問A901.KENT WEBさんのカウンタで、「IPアドレスの二重カウントチェック」を有効にしても訪問者数が多く計数されてしまう。
このIPアドレスを蓄積する処理というのは、蓄積数が多くなると処理が重たくなります。これをアクセスカウンタとアクセスレポートの両方で行なうことは無駄ですので、アクセスカウンタ側から、カウントした時だけアクセスレポートのCGIを呼び出すように改造しました。
このページでは、元のスクリプトを極力変更しない(変更が容易)ように改造した例を示しています。(かなり強引で、スクリプトを汚しています)改造内容を理解された方は、著作権表示を削除されないよう細心の注意を払って、更なる改造を行なって下さい。
SSI方式のCGIは、下記に示しますように、その骨格は実に簡単です。
これを前提にKENT WEBさんより、日計式テキストカウンタ (SSI式)をダウンロードし、一度そのスクリプトを読んで理解して下さい。
SSIカウンタの動作原理が理解できましたら、目的のCGIスクリプトの改造を行ないます。
最も簡単なSSI方式のCGI(perl)例
#!/usr/local/bin/perl # 最も簡単なSSI方式のCGI(perl)例 # SSI用のヘッダをヘッダ出力します。 print "Content-type: text/plain\n\n"; # 記録ファイルからカウント数を読み込み open(IN,"./counter.log") || die ("./counter.logがopenできません。"); $count = <IN>; close(IN); # カウント数に1を加算 $count++; # カウント数を記録ファイルに書き出し open(OUT,">./counter.log") || die ("./counter.logが出力用としてopenできません。"); print OUT $count; close(OUT); # テキストカウンタを出力。カウント数のところには変数$countを記入します。 print "<span style=\"color:white; background-color:black; font-size:1.2em\">$count</span>人目のご訪問者です。\n"; exit;
KENT WEBさんより、日計カウンタ2とAccess Reportをダウンロードし、設置します。
各々のCGIが下記のURI
http://www.example.com/cgi/count/dayx.cgi?check
http://www.example.com/cgi/report/report.cgi?check
で、正常に設置されていることを確認できて、正常にアクセスカウンタが動作しているものとします。
http://www.example.com/cgi/count/dayxmgr.cgiで、日次アクセス一覧と月次アクセス一覧が正常に表示されていることを確認します。
http://www.example.com/cgi/report/replist.cgiで、アクセスレポートが正常に表示されていることを確認します。
次項の改造例のような形でスクリプトの改造を行ないます。改造したファイルは、別のファイル名で、保存します。
(本例では、dayxSSI.cgiとreportSSI.cgiとしています。)
ホームページ(トップページ)のhtmlファイルに<!--#exec cgi="/cgi/count/dayxSSI.cgi"-->と記述して起動し、動作の確認を行います。countディレクトリ内には、あらかじめipa.datという空ファイルを作成しておいて下さい。
このカウンタは、アクセスされたIPアドレスを指定個数分、ipa.datに溜め込みますので、LAN内で事前に動作確認を行なう場合は、dayxSSI.cgiの$ip_checkと$ipa_numを「0」にして動作確認を行なって下さい。
申し訳ありませんが、これらの改造ファイルの再配布は、行ないません。理由は、元のスクリプトのバージョンが上がった場合に保守しないからです。必ず、スクリプトを読んで自己責任で改造し、動作確認を行なって下さい。尚、「次項の改造例の実際」の青枠内をコピーして使用される場合は、私の意志とは関係なく、再配布という形になります。その際はKENT WEBさんの「CGIスクリプト利用規定」および「再配布の規定について」に記載されている事項にすべて同意された上で、コピーして下さい。
KENT WEBさんのDAY COUNTER-EX v3.41のテキストカウンタへの改造例
下記にKENT WEBさんのDAY COUNTER-EX(dayx.cgi)が実行パス下の/cgi/count/dayx.cgiに設置されている場合の改造例を示します。改造したファイルは、同じディレクトリ(フォルダ)にdayxSSI.cgiというファイル名で保存した場合を示しています。トップページのhtmlファイルに<!--#exec cgi="/cgi/count/dayxSSI.cgi"-->と書きますと、そこに項1の様なカウント表示が挿入されます。
KENT WEBさんのDAY COUNTER-EX v3.41のテキストカウンタへの改造例
#!/usr/local/bin/perl #┌───────────────────────────────── #│ DAY COUNTER-EX v3.4 (2002/06/19) #│ Copyright(C) KENT WEB 2002 #│ webmaster@kent-web.com #│ http://www.kent-web.com/ #└───────────────────────────────── $ver = 'DayX v3.4'; #┌───────────────────────────────── #│ [注意事項] #│ 1. このスクリプトはフリーソフトです。このスクリプトを使用した #│ いかなる損害に対して作者は一切の責任を負いません。 #│ 2. 設置に関する質問はサポート掲示板にお願いいたします。 #│ 直接メールによる質問は一切お受けいたしておりません。 #└───────────────────────────────── # # [タグの書き方の例] # SSI方式では、<!--#exec cgi="/cgi/count/dayxSSI.cgi"-->とします。(Webサーバーの絶対パスを使用すること!) # SSI方式に改造後は、下記の方法は使用できなくなります。 # 総カウント数 <img src="count/dayx.cgi?gif"> <--- 使用できない。 # 本日のカウント数 <img src="count/dayx.cgi?today"> <--- 使用できない。 # 昨日 〃 <img src="count/dayx.cgi?yes"> <--- 使用できない。 # # [日計/月次一覧を見る] # http://www.host.xxx/~user/count/dayxmgr.cgi <--- そのまま使用します。 # # [チェックのしかた (ブラウザから最後に ?check をつけて呼出す)] # http://www.host.xxx/~user/count/dayx.cgi?check <--- そのまま使用します。 # ------------------------------------------------------------------- # [ディレクトリ構成例 (括弧内はパーミッション) ] # # public_html / index.html ... ここにカウンタを設置 # | # +-- count / dayx.cgi [755] <--- オリジナルのCGIをそのままにしておきます。未使用。 # | dayxSSI.cgi [755] <--- dayx.cgiを改造して使用します。 # | dayxmgr.cgi [755] <--- そのまま使用します。 # | gifcat.pl [644] <--- 不要になります。 # | dayx.dat [666] <--- そのまま使用します。 # | day.dat [666] <--- そのまま使用します。 # | mon.dat [666] <--- そのまま使用します。 # | blue.gif <--- そのまま使用します。 # | red.gif <--- そのまま使用します。 # | ipa.dat [666] <--- 追加します。☆★☆あらかじめ空ファイルを作成しておいて下さい。☆★☆ # | # +-- gif1 / 1.gif 2.gif ... 0.gif <--- 不要になります。 # | # +-- gif2 / 1.gif 2.gif ... 0.gif <--- 不要になります。 # | # +-- lock [777] / #============# # 設定項目 # #============# # アクセスレポートライブラリ $report = '../report/reportSSI.cgi'; # <--- 改造したアクセスレポートCGIファイルを相対パスで設定。 # 画像連結ライブラリ取込み #require './gifcat.pl'; # <--- テキストカウンタなので、使用しなくて済むようになります。 # 総カウント数の桁数 #$digit1 = 6; <------------- 無指定で可。前0を入れる場合は、カウンタのタグで$countの代わりにsprintf("%06d",$count)とします。 # 本/昨日カウント数の桁数 #$digit2 = 3; <------------- 無指定で可。前0を入れる場合は、カウンタのタグで$todayの代わりにsprintf("%03d",$today)とします。 # カウンタのタグで$yes の代わりにsprintf("%03d",$yes )とします。 # ログファイル $logfile = "./dayx.dat"; # 日次記録ファイル $dayfile = "./day.dat"; # 月次記録ファイル $monfile = "./mon.dat"; # 総カウント数用GIF画像のディレクトリ #$gifdir1 = "./gif1"; <----- 使用しなくて済むようになります。 # 本/昨日カウント数用GIF画像のディレクトリ #$gifdir2 = "./gif2"; <----- 使用しなくて済むようになります。 # ファイルロック機構 # 0 : しない # 1 : する (symlink関数式) <--- UNIX用 # 2 : する (mkdir関数式) <----- Windows用 $lockkey = 2; # ロックファイル名 $lockfile = "./lock/dayx.lock"; # カウンタの機能タイプ # 0 : 総カウント数不要(昨日/本日のみ) # 1 : 標準タイプ #$type = 1; <--------------- 使用しなくて済むようになります。 # IPアドレスの二重カウントチェック # 0 : チェックしない # 1 : チェックする $ip_check = 1; # IPアドレス記録数で指定したIPアドレスは、記録されカウントされません。 # 従って、自サイトのURLから、カウンタを設置しているトップページに # 戻ってくるカウントを防止出来ます。 # IPアドレス記録ファイル $ipalogfile = "./ipa.dat"; # IPアドレス記録数(2以上にしないと機能しません。) # 1日の総カウント数の約10分の1を目安にしますと、 # 2時間程度のIPアドレスを溜め込むことが出来ます。 $ipa_num = 10; # LAN 内のプライベートセグメントアドレス(192.168.1.0)を設定して、 # LAN 内からのアクセス(192.168.1.*)に対して、カウントされないようにします。 # 分からなければ、ルータやクライアント機のIPアドレスを入力して下さい。 $PrivateSegAddr = '192.168.1.0'; #============# # 設定完了 # #============# # 引数を解釈 $mode = $ENV{'QUERY_STRING'}; $mode =~ s/\W//g; # 更新系処理でないならば 1 秒待たせる #if (($type == 1 && $mode eq "yes") || ($type == 1 && $mode eq "today") || ($type == 0 && $mode eq "yes")) { sleep(1); } # チェックモード #if (!$mode || $mode eq 'check') { ✓ } if ($mode eq 'check') { ✓ } # ヘッダ出力 <--------------------- SSI用のヘッダを追加します。 print "Content-type: text/plain\n\n"; # ロック開始 #if (($type && $mode eq "gif" && $lockkey) || (!$type && $mode eq "today" && $lockkey)) { &lock; } if ($lockkey) { &lock; } # <--------- 1回の呼び出しで3個のカウンタ値を全て返すので、条件を簡単にします。 # 記録ファイルから読み込み open(IN,"$logfile") || &error ("$logfileがopenできません。") ; $data = <IN>; close(IN); # 記録ファイルを分解 ($key,$yes,$today,$count,$youbi,$ip) = split(/<>/, $data); # 日時を取得 $ENV{'TZ'} = "JST-9"; ($mday,$mon,$year,$wday) = (localtime(time))[3..6]; @week = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat'); $year += 1900; $mon++; $thisday = $week[$wday]; if ($mon < 10) { $mon = "0$mon"; } $date = "$year\/$mon"; # IPチェック $host = $ENV{'REMOTE_HOST'}; # <----- report.cgiスクリプトへ引数として与える。 $addr = $ENV{'REMOTE_ADDR'}; $flag=0; if ($ip_check) { # $addr = $ENV{'REMOTE_ADDR'}; if ($addr eq $ip) { $flag=1; } # LAN 内からのアクセスに対して、カウントされないようにします。 if (pack("C3", split(/\./, $addr)) eq pack("C3", split(/\./, $PrivateSegAddr))) { $flag=1; } if (!$flag && $ipa_num > 1) { # IPアドレス記録データの読み込み open(IN,"$ipalogfile") || &error ("$ipalogfileがopenできません。") ; $ipadata = <IN>; close(IN); # IPアドレス記録ファイルを分解して、配列に格納 @ipa[0..$ipa_num - 2] = split(/<>/, $ipadata); # IPアドレスチェック(アクセスしたIPアドレスが存在したら、カウントしない) foreach (@ipa) { if ($_ eq $addr) { $flag=1; last; } } } if (!$flag) { # アクセスしたIPアドレスが存在しなかったら、 # IPアドレスを追加して、記録ファイル様式に変換 $ipa = $addr . '<>'; foreach (@ipa) { $ipa .= $_ . '<>'; } # IPアドレス記録ファイルを更新 open(OUT,">$ipalogfile") || &error ("$logfileが出力用としてopenできません。") ; print OUT $ipa; close(OUT); } } # 本日のカウント数をキーにカウントアップ #if ((!$flag && $type && $mode eq 'gif') || (!$flag && !$type && $mode eq 'today')) { if (!$flag) { # <-------------------- 1回の呼び出しで3個のカウンタ値を全て返すので、条件を簡単にします。 $count++; ## 当日処理 if ($key eq $mday) { $today++; # ログをフォーマット $data = "$key<>$yes<>$today<>$count<>$thisday<>$addr<>\n"; } ## 翌日処理 else { # ログをフォーマット $data = "$mday<>$today<>1<>$count<>$thisday<>$addr<>\n"; &day_count; &mon_count; } # ログを更新 open(OUT,">$logfile") || &error ("$logfileが出力用としてopenできません。") ; print OUT $data; close(OUT); } # ロック解除 if ($lockflag) { &unlock; } # カウンタ画像出力 #if ($mode eq "gif") { &count_view($count, $digit1, $gifdir1); } <--- 1回の呼び出しなので、未使用。 #elsif ($mode eq "yes") { &count_view($yes, $digit2, $gifdir2); } #else { &count_view($today, $digit2, $gifdir2); } #exit; # テキストカウンタ出力 # ここにトップページに表示するカウンタのタグを書きます。 # ただし、総合カウント数には$countを、本日カウント数には$todayを、昨日カウント数には$yesを # カウント数の代わりに記入します。 print <<"_HTML_"; <span style="color:red">開始日:200X年XX月XX日</span>より貴方は、 <span style="color:white; background-color:black; font-size:1.2em; font-family:'Times New Roman',sans-serif"> $count </span>人目<small>(本日:<strong>$today</strong>人、昨日:<strong>$yes</strong>人)</small> のご訪問者です。<br /> <small>このカウンタは、<strong><a href=\"http://www.kent-web.com/\">DAY COUNTER-EXとAccess Report</a></strong>を SSI方式に改造して使用しています。<br /> ご訪問者数の計数は、クッキーを発行せずにトップページにアクセスされたIPアドレスを$ipa_num個保持して、 重複計数を排除しています。</small><br /> _HTML_ # アクセス解析システム出力 if (!$flag) { require $report || &error ("$reportがrequireできません。"); # <--- アクセスレポートを読み込み。 &report ($lockkey,$ip_chk,$host,$addr); # <----------------------- アクセスレポートを実行。 } exit; #-------------------# # カウンタ出力処理 # #-------------------# #sub count_view { <--- テキストカウンタに変更したので、この回路は未使用に。 # local(@view); # local($data, $digit, $dir) = @_; # # # 桁数調整 # while (length($data) < $digit) { $data = '0' . $data; } # # @view=(); # foreach (split(//, $data)) { # push(@view,"$dir/$_\.gif"); # } # # # 画像連結 # print "Content-type: image/gif\n\n"; # binmode(STDOUT); # print &gifcat'gifcat(@view); #} #--------------# # ロック処理 # #--------------# sub lock { local($retry) = 5; if (-e $lockfile) { local($mtime) = (stat($lockfile))[9]; if ($mtime < time - 60) { &unlock; } } # symlink関数式ロック if ($lockkey == 1) { while (!symlink(".", $lockfile)) { if (--$retry <= 0) { &error ("$lockfileが作成できません。") ; } sleep(1); } # mkdir関数式ロック } elsif ($lockkey == 2) { while (!mkdir($lockfile, 0755)) { if (--$retry <= 0) { &error ("$lockfileが作成できません。") ; } sleep(1); } } $lockflag=1; } #--------------# # ロック解除 # #--------------# sub unlock { if ($lockkey == 1) { unlink($lockfile); } elsif ($lockkey == 2) { rmdir($lockfile); } $lockflag=0; } #------------------------# # 日次カウント数の処理 # #------------------------# sub day_count { # ログの日次キーより本日の日が小さければ月が変わったと判断する if ($mday < $key) { open(DB,">$dayfile") || &error ("$dayfileがopenできません。") ; close(DB); } # 月内での処理 else { if ($key < 10) { $key = "0$key"; } open(DB,">>$dayfile") || &error ("$dayfileに追記openできません。") ; print DB "$mon\/$key \($youbi\)<>$today<>\n"; close(DB); } } #------------------------# # 月間カウント数の処理 # #------------------------# sub mon_count { # 初めてのアクセスの場合 if (-z $monfile) { $mons[0] = "$date<>$today<>\n"; } else { open(IN,"$monfile") || &error ("$monfileがopenできません。") ; @mons = <IN>; close(IN); # ログ配列の最終行を分解 $mons[$#mons] =~ s/\n//; ($y_m,$cnt) = split(/<>/,$mons[$#mons]); # 当月処理 if ($y_m eq $date) { $cnt = $cnt + $today; $mons[$#mons] = "$y_m<>$cnt<>\n"; } # 翌月処理 #(ログ配列の最終行が $dateと異なれば、月が変ったと判断する) else { $cnt = $cnt + $today; $mons[$#mons] = "$y_m<>$cnt<>\n"; push(@mons,"$date<>0<>\n"); } } # ログファイルを更新 open(OUT,">$monfile") || &error ("$monfileが書き込み用としてopenできません。") ; print OUT @mons; close(OUT); } #--------------# # エラー処理 # #--------------# sub error { print "エラーが発生しました。$_[0] : $!\n"; if ($lockflag) { &unlock; } # @err = ('47','49','46','38','39','61','2d','00','0f','00','80','00','00','00','00','00','ff','ff','ff','2c', '00','00','00','00','2d','00','0f','00','00','02','49','8c','8f','a9','cb','ed','0f','a3','9c','34', '81','7b','03','ce','7a','23','7c','6c','00','c4','19','5c','76','8e','dd','ca','96','8c','9b','b6', '63','89','aa','ee','22','ca','3a','3d','db','6a','03','f3','74','40','ac','55','ee','11','dc','f9', '42','bd','22','f0','a7','34','2d','63','4e','9c','87','c7','93','fe','b2','95','ae','f7','0b','0e', '8b','c7','de','02','00','3b'); # # print "Content-type: image/gif\n\n"; # foreach (@err) { # $data = pack('C*',hex($_)); # print $data; # } exit; } #------------------# # チェックモード # #------------------# sub check { print "Content-type: text/html\n\n"; print "<html><head><title>DAY COUNTER-EX</title></head>\n"; print "<body>\n", "<h2>Check Mode</h2>\n", "<UL>\n"; # # ログファイル確認 # foreach ($logfile, $dayfile, $monfile) { # # パス # if (-e $_) { # print "<LI>$_ → パス OK!\n"; # # # パーミッション # if (-r $_ && -w $_) { print "<LI>$_ → パーミッション OK!\n"; } # else { print "<LI>$_ → パーミッション NG!\n"; } # } else { print "<LI>$_ → パス NG!\n"; } # } # # # 画像ディレクトリ # foreach ($gifdir1, $gifdir2) { # if (-d $_) { # print "<LI>$_ → 画像ディレクトリ OK!\n"; # # # 画像 # foreach $n (0 .. 9) { # if (-e "$_\/$n\.gif") { print "<LI>$_\/$n\.gif → OK!\n"; } # else { print "<LI>$_\/$n\.gif → NG!\n"; } # } # } else { print "<LI>$_\/$n\.gif → 画像ディレクトリ NG!\n"; } # } # # # ロックディレクトリ # print "<LI>ロック形式:"; # if ($lockkey == 0) { print "ロック設定なし\n"; } # else { # if ($lockkey == 1) { print "symlink\n"; } # else { print "mkdir\n"; } # ($lockdir) = $lockfile =~ /(.*)[\\\/].*$/; # print "<LI>ロックディレクトリ:$lockdir\n"; # # if (-d $lockdir) { # print "<LI>ロックディレクトリ : パス OK\n"; # if (-r $lockdir && -w $lockdir && -x $lockdir) { # print "<LI>ロックディレクトリ : パーミッション OK\n"; # } else { # print "<LI>ロックディレクトリ : パーミッション NG → $lockdir\n"; # } # } else { print "<LI>ロックディレクトリ : パス NG → $lockdir\n"; } # } # # 著作権表示:削除禁止 <--- ここは、削除してはいけない。 print "<P><!-- $ver -->\n"; print "- <a href=\"http://www.kent-web.com/\">$ver</a> 改造:○○○-\n"; # <--- ○○○は、貴方のお名前を記入。 require $report || &error ("$reportがrequireできません。"); # <--------- アクセスレポートを読み込み。 &check_report; # <------------------------------------------------------ アクセスレポートを実行。 print "</UL>\n</body></html>\n"; exit; }
KENT WEBさんのAccess Report v3.0の改造例
下記にKENT WEBさんのAccess Report(report.cgi)が実行パス下の/cgi/report/report.cgiに設置されている場合の改造例を示します。改造したファイルは、同じディレクトリ(フォルダ)にreportSSI.cgiというファイル名で保存した場合を示しています。基本的にテキストカウンタのCGI(dayxSSI.cgi)より、必要に応じて呼び出されて実行する形になります。重複するサブルーチンなどは無駄ですから、コメント行に変更しています。
KENT WEBさんのAccess Report v3.0の改造例
#!/usr/local/bin/perl #┌───────────────────────────────── #│ Access Report v3.0 (2002/10/24) #│ アクセス解析システム #│ Copyright(C) Kent Web 2002 #│ webmaster@kent-web.com #│ http://www.kent-web.com/ #└───────────────────────────────── $ver = 'Access Report v3.0'; #┌───────────────────────────────── #│ [注意事項] #│ 1. このスクリプトはフリーソフトです。このスクリプトを使用した #│ いかなる損害に対して作者は一切の責任を負いません。 #│ 2. 設置に関する質問はサポート掲示板にお願いいたします。 #│ 直接メールによる質問は一切お受けいたしておりません。 #└───────────────────────────────── # # [ (1) CGIモード : タグの貼り付け方例 ] # *以下のタグを、必ず <body>~</body> 間に記述して下さい # <SCRIPT Language="JavaScript"> <--- 使用しない。 # <!-- # document.write("<img src='http://~~/cgi-bin/report.cgi?"); # document.write(document.referrer+"' width=1 height=1>"); # // --> # </SCRIPT> # # [ (2) SSIモード : タグの貼り付け方例 ] # # <!--#exec cgi="./cgi-bin/report.cgi"--> <--- 使用できない。このスクリプトは、dayxSSI.cgiより呼び出される形にします。 # # # [ ファイル構成例 ] かっこ内はパーミッション値 # # public_html / index.html # | # +-- report / report.cgi [755] <--- オリジナルのCGIをそのままにしておきます。未使用。 # | reportSSI.cgi [755] <--- report.cgiを改造して使用します。 # | report.log [666] <--- そのまま使用します。 # | replist.cgi [755] <--- そのまま使用します。 # | jcode.pl [644] <--- そのまま使用します。 # | graph1.gif <--- そのまま使用します。 # | graph2.gif <--- そのまま使用します。 # | # +-- lock [777] / sub report { # <--------------------------- reportサブルーチンとして実行可能とする。 ($lockkey,$ip_chk,$host,$addr) = @_; # <--- このスクリプトを呼び出すdayx.cgiよりデータを受けます。 # これを書かなくても動きますが、コメントとして入れています。 #============# # 基本設定 # #============# # 文字コードライブラリ ./は、カレントディレクトリですが、このスクリプトを呼び出すdayx.cgiのディレクトですから、ご注意! #$jcode = 'D:/www/cgi/report/jcode.pl'; # <--- 絶対パスで設定しておく方法でも可。 $jcode = '../report/jcode.pl'; # <------- このスクリプトを呼び出すdayx.cgiのディレクトからの相対パスで設定します。<------- ※1 # SSIモード (0=no 1=yes) # → SSIの利用可能なサーバ限定(呼び出しタグに注意) $ssi = 1; # ログファイル #$logfile = 'D:/www/cgi/report/report.log'; # <--- 絶対パスで設定しておく方法でも可。 $logfile = '../report/report.log'; # <--- このスクリプトを呼び出すdayx.cgiのディレクトからの相対パスで設定します。 # 最大ログ保持数(これ以上大きくしない方が無難) $max = 1000; # アトランダム機能 # → 0以外で有効。その数値の確率でしかログ保存を行わない。 # → 1日当りの訪問回数が上記$max回を超えるサイトの場合、時間帯の平準化を行う機能。 # → 例:$rand=100; であれば、確率的に100回に1度しか集計を行わない。 $rand = 0; # リンク元除外ページ(半角スペースで区切る) # → ここで指定したURLは「リンク元」集計から除外されます # → 例:$myurl = 'http://www.xxx.yyy/~foo/ http://www.zzz.xx.jp/'; $myurl = ''; # ファイルロック機構 # 0 : しない # 1 : する (symlink関数式) <--- UNIX用 # 2 : する (mkdir関数式) <----- Windows用 #$lockkey = 0; <----------------- このスクリプトを呼び出すdayx.cgiより引数として与えられるので不要。 # ロックファイル名 ./は、カレントディレクトリですが、このスクリプトを呼び出すdayx.cgiのディレクトですから、ご注意! $lockfile = './lock/report.lock'; # IPアドレスチェックによる二重記録排除 (0=no 1=yes) #$ip_chk = 0; <------------------ このスクリプトを呼び出すdayx.cgiより引数として与えられるので不要。 #============# # 設定完了 # #============# # チェックモード if ($ENV{'QUERY_STRING'} eq "check") { &check_report; } # ランダム開始 if ($rand > 0) { srand; local($rand2) = int(rand($rand)); if ($rand2 != 0) { &view; exit; } } # リンク元を取得 if ($ssi) { $ref = $ENV{'HTTP_REFERER'}; } else { $ref = $ENV{'QUERY_STRING'}; } $ref =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; if ($ref =~ /^https?\:\/\//) { $ref =~ s/&/&/g; $ref =~ s/"/"/g; $ref =~ s/</</g; $ref =~ s/>/>/g; $ref =~ s/\r|\n|\0//g; # シフトJIS変換(検索エンジンからのキーワード処理) require $jcode; # <-------------------------------------------------------------------------------- ※1 &jcode'convert(*ref,'sjis'); # <------------------------------------------------------------------- ※1 } else { $ref=''; } # リンク元集計での除外指定処理 if ($myurl) { $flag=0; foreach (split(/\s+/, $myurl)) { if ($ref =~ /$_/i) { $flag=1; last; } } if ($flag) { $ref=''; } } # ブラウザ情報を取得 <--- ※このスクリプトのバージョンアップは、このブラウザ情報の追加がほとんどです。 $agent2 = $agent = $ENV{'HTTP_USER_AGENT'}; if ($agent =~ /AOL/) { $agent = 'AOL'; } elsif ($agent =~ /Opera/i) { $agent = 'Opera'; } elsif ($agent =~ /MSIE 3/i) { $agent = 'MSIE 3'; } elsif ($agent =~ /MSIE 4/i) { $agent = 'MSIE 4'; } elsif ($agent =~ /MSIE 5/i) { $agent = 'MSIE 5'; } elsif ($agent =~ /MSIE 6/i) { $agent = 'MSIE 6'; } elsif ($agent =~ /Mozilla\/2/i) { $agent = 'Netscape 2'; } elsif ($agent =~ /Mozilla\/3/i) { $agent = 'Netscape 3'; } elsif ($agent =~ /Mozilla\/4/i) { $agent = 'Netscape 4'; } elsif ($agent =~ /Netscape ?6/i) { $agent = 'Netscape 6'; } elsif ($agent =~ /Netscape\/7/i) { $agent = 'Netscape 7'; } elsif ($agent =~ /Mozilla\/5/i) { $agent = 'Mozilla'; } elsif ($agent =~ /Netscape/i && $agent =~ /Gecko/i) { $agent = 'Mozilla'; } elsif ($agent =~ /Lynx/i) { $agent = 'Lynx'; } elsif ($agent =~ /Cuam/i) { $agent = 'Cuam'; $os = 'Windows'; } elsif ($agent =~ /Ninja/i) { $agent = 'Ninja'; $os = 'Windows'; } elsif ($agent =~ /WWWC/i) { $agent = 'WWWC'; $os = 'Windows'; } elsif ($agent =~ /DoCoMo/i) { $agent = $os = 'DoCoMo'; } elsif ($agent =~ /J-PHONE/i) { $agent = $os = 'J-PHONE'; } elsif ($agent =~ /UP\.Browser/i) { $agent = $os = 'EZweb'; } elsif ($agent =~ /L\-mode/i) { $agent = $os = 'L-mode'; } elsif ($agent =~ /ASTEL/i) { $agent = $os = 'ASTEL'; } elsif ($agent =~ /PDXGW/i) { $agent = $os = 'H"'; } if ($agent2 =~ /win[dows ]*95/i) { $os = 'Win95'; } elsif ($agent2 =~ /win[dows ]*9x/i) { $os = 'WinMe'; } elsif ($agent2 =~ /win[dows ]*98/i) { $os = 'Win98'; } elsif ($agent2 =~ /win[dows ]*XP/i) { $os = 'WinXP'; } elsif ($agent2 =~ /win[dows ]*NT ?5\.1/i) { $os = 'WinXP'; } elsif ($agent2 =~ /Win[dows ]*NT ?5/i) { $os = 'Win2000'; } elsif ($agent2 =~ /win[dows ]*2000/i) { $os = 'Win2000'; } elsif ($agent2 =~ /Win[dows ]*NT/i) { $os = 'WinNT'; } elsif ($agent2 =~ /Win[dows ]*CE/i) { $os = 'WinCE'; } elsif ($agent2 =~ /shap pda browser/i) { $os = 'ZAURUS'; } elsif ($agent2 =~ /Mac/i) { $os = 'Mac'; } elsif ($agent2 =~ /X11/ || $agent2 =~ /SunOS/i || $agent2 =~ /Linux/i || $agent2 =~ /HP-UX/i || $agent2 =~ /FreeBSD/i || $agent2 =~ /NetBSD/i || $agent2 =~ /OSF1/i || $agent2 =~ /IRIX/i) { $os = 'UNIX'; } # ホスト名を取得 #$host = $ENV{'REMOTE_HOST'}; <----- このスクリプトを呼び出すdayx.cgiより引数として与えられるので不要。 #$addr = $ENV{'REMOTE_ADDR'}; <----- このスクリプトを呼び出すdayx.cgiより引数として与えられるので不要。 #if ($host eq "" || $host eq $addr) { # $host = gethostbyaddr(pack("C4",split(/\./,$addr)),2) || $addr; #} if ($host =~ /(.*)\.(\d+)$/) { ; } elsif ($host =~ /(.*)\.(.*)\.(.*)\.(.*)$/) { $host = "\*\.$2\.$3\.$4"; } elsif ($host =~ /(.*)\.(.*)\.(.*)$/) { $host = "\*\.$2\.$3"; } # 時間を取得 $ENV{'TZ'} = "JST-9"; $hour = (localtime(time))[2]; # ロック開始 &lock if ($lockkey); # ログ読み込み open(IN,"$logfile") || &error("Open Error : $logfile"); @lines = <IN>; close(IN); $top = shift(@lines); # IPチェック if ($ip_chk) { chop($top); if ($addr eq $top) { &unlock; # &view; exit; } } # 記事数を調整し、新規ログをフォーマット while ($max-1 < @lines) { pop(@lines); } unshift(@lines,"$agent<>$os<>$host<>$ref<>$hour<>\n"); unshift(@lines,"$addr\n"); # 更新 open(OUT,">$logfile") || &error("Write Error : $logfile"); print OUT @lines; close(OUT); # ロック解除 &unlock if ($lockkey); # 表示 #&view; exit; } # reportサブルーチンの終わり <--- ここまでをreportサブルーチンとします。 #------------# # 結果表示 # #------------# #sub view { # if ($ssi) { # print "Content-type: text/plain\n\n"; # } else { # # 透過GIF定義 # local(@dmy) = ( # '47','49','46','38','39','61','02','00','02','00','80', # '00','00','00','00','00','ff','ff','ff','21','f9','04', # '01','00','00','01','00','2c','00','00','00','00','02', # '00','02','00','00','02','02','8c','53','00','3b', # ); # # print "Content-type: image/gif\n\n"; # binmode STDOUT; # foreach (@dmy) { # print pack('C*',hex($_)); # } # } #} #--------------# # ロック処理 # #--------------# #sub lock { # <----- このスクリプトを呼び出すdayx.cgiと重複するので不要。 # local($retry) = 5; # # # 1分以上古いロックは削除する # if (-e $lockfile) { # local($mtime) = (stat($lockfile))[9]; # if ($mtime < time - 60) { &unlock; } # } # # symlink関数式ロック # if ($lockkey == 1) { # while (!symlink(".", $lockfile)) { # if (--$retry <= 0) { &error('LOCK is BUSY'); } # sleep(1); # } # # mkdir関数式ロック # } elsif ($lockkey == 2) { # while (!mkdir($lockfile, 0755)) { # if (--$retry <= 0) { &error('LOCK is BUSY'); } # sleep(1); # } # } # $lockflag=1; #} #--------------# # ロック解除 # #--------------# #sub unlock { # <----- このスクリプトを呼び出すdayx.cgiと重複するので不要。 # if ($lockkey == 1) { unlink($lockfile); } # elsif ($lockkey == 2) { rmdir($lockfile); } # $lockflag=0; #} #--------------# # エラー処理 # #--------------# #sub error { # <----- このスクリプトを呼び出すdayx.cgiと重複するので不要。 # &unlock if ($lockflag); # die "$_[0] : $!"; #} #------------------# # チェックモード # #------------------# sub check_report { # <--- このスクリプトを呼び出すdayx.cgiと重複するので変更(このスクリプトをpackage化していない為)。 # print "Content-type: text/html\n\n"; # print "<html><head><title>$ver</title></head>\n"; # print "<body>\n<UL>\n"; # # # jcode.plの位置チェック # if (-e $jcode) { print "<LI>jcode.pl の位置 : OK!\n"; } # else { print "<LI>jcode.pl の位置が不正です : $jcode\n"; } # # # ログファイルのパス確認 # if (-e $logfile) { # print "<LI>ログファイルのパス : OK!\n"; # # # パーミッション # if (-r $logfile && -w $logfile) { # print "<LI>ログファイルのパーミッション : OK!\n"; # } else { # print "<LI>ログファイルのパーミッション : NG → $logfile\n"; # } # } else { print "<LI>ログファイルがありません : $logfile\n"; } # # # ロックディレクトリ # print "<LI>ロック形式:"; # if ($lockkey == 0) { print "ロック設定なし\n"; } # else { # if ($lockkey == 1) { print "symlink\n"; } # else { print "mkdir\n"; } # # ($lockdir) = $lockfile =~ /(.*)[\\\/].*$/; # print "<LI>ロックディレクトリ:$lockdir\n"; # # if (-d $lockdir) { # print "<LI>ロックディレクトリのパス:OK\n"; # # if (-r $lockdir && -w $lockdir && -x $lockdir) { # print "<LI>ロックディレクトリのパーミッション:OK\n"; # } else { # print "<LI>ロックディレクトリのパーミッション:NG → $lockdir\n"; # } # } else { print "<LI>ロックディレクトリのパス:NG → $lockdir\n"; } # } # 著作権表示:削除禁止 <--- ここは、削除してはいけない。 print "<P><span style='font-size:9pt;font-family:Verdana'><!-- $ver -->\n"; print "- <a href='http://www.kent-web.com/'>Access Report</a> 改造:○○○-\n"; # <--- ○○○は、貴方のお名前を記入。 print "</span>\n</UL>\n</body>\n</html>\n"; exit; } 1; # <--- requireで呼び出されるスクリプトの最後には、これが必要。 __END__
※1で示しました部分は、アクセス元のrefererがUTF-8の場合、文字化けします。詳細は次項の改造を実施して下さい。
Access Reportで、リンク元の取得情報を表示すると、一部文字化けを起こし、肝心の検索キーがわからないものがあります。これは、検索エンジンのGoogleなどが、UTF-8コードを使用しているからです。これについては、文字コードライブラリをJcode.plからJcode.pmに変更したり、perlのバージョンを5.8以降にし、Encodeモジュールを使用することで、解決できます。
perlのバージョンが5.6以前
Jcode.pmをインストールする必要があります。Windows用Jcode.pmのインストール方法を参考に前項の※1の三箇所の書式を変更して下さい。
perlのバージョンが5.8以後
以下の様に該当行の3行をコメント行に変更して、Encodeモジュールを使用する書式に変更します。
# 文字コードライブラリ $jcode = '../report/jcode.pl'; は、 #$jcode = '../report/jcode.pl'; # <------- コメント行にします。 --------------------------------------------------------------------------------------- # シフトJIS変換(検索エンジンからのキーワード処理) #require $jcode; #&jcode'convert(*ref,'sjis'); の2行は、以下の様に変更します。 # 日本語コード変換 Encode モジュールの使用を宣言する。(Perl 5.8以降は、標準モジュール) use Encode qw/ from_to /; use Encode::Guess qw/ euc-jp shiftjis 7bit-jis /; # 変換元のコードを調べる。 $type_enc = guess_encoding ( $ref_data ); # 変換先のコードを設定する。 $type_to = $EDIT_CODE; # 変換元のコードを設定する。 eval { $type_from = $type_enc -> name; }; if ($@) { # 変換元のコードがわからない場合 ######### この部分は、本来は不要。 ######### $type_from = code_fumei ( $ref_data , $type_enc , $type_to ); } # 変換元のコードから編集に使用しているコードに変換(半角カナを全角カナには、変換しない。) from_to ( $ref_data , $type_from , $type_to ); 以下の行は、サブルーチンです。スクリプトに追加して下さい。 sub code_fumei { ######### この部分は、今後の課題 ######### #--------------------------- # 変換元コードの類推 #--------------------------- # 処理:guess_encoding ( $referer )で、$refererの使用コードを調べて不明な場合、使用コードを類推する。 # # 入力:データファイル$referer , $encode不明なコード , $type_to変換先コード # 出力:使用コード$type # # 不明なコードの場合、$encodeには、"shiftjis or euc-jp or utf8"の様な文字列が入っている。 # my ( $referer , $encode , $type_to ) = @_; # 引数の設定 my ( $type , $ref_test ); ######### この部分は、今後の課題 ######### # 変換元のコードがわからない場合、アクセス元のホスト名を手がかりに # 使用コード$typeを決めていく。 $type = "euc-jp"; # まず、基本的にEUC(拡張UNIXコード)に設定する。 if ( $referer =~ /(search.fresheye.com|www.goo.ne.jp|.infoseek.co.jp|search.naver.co.jp|www.tocc.co.jp)/ ) { ; } elsif ( $referer =~ /(verno.ueda.info.waseda.ac.jp|google.yahoo.co.jp|.odn.ne.jp|search.biglobe.ne.jp)/ ) { ; } elsif ( $referer =~ /(Shift_JIS|excite.co.jp|search.nifty.com|.msn.com|.netscape.com)/ ) { $type = "shiftjis"; } elsif ( $referer =~ /(www.altavista.com|www.excite.co.jp|www.alltheweb.com|.lycos.co.jp)/ ) { $type = "shiftjis"; } elsif ( $referer =~ /cache:/ ) { $type = "utf8"; } elsif ( $referer =~ /.google./ ) { if($referer =~ /(UTF-8|utf-8)/) { $type = "utf8"; } else { $type = "shiftjis"; } } else { # アクセス元のホスト名が定義されていない場合 if ( $encode =~ /utf8/ ) { # UTF-8の可能性がある場合は、UTF-8コードにする。 $type = "utf8"; } } if ( $type eq "euc-jp" ) { # EUC(拡張UNIXコード)で変換して見る。 $type = "euc-jp"; $ref_test = $referer; from_to ( $ref_test , $type , $type_to ); # EUC(拡張UNIXコード)で変換した結果、「?」の文字が3つ以上あれば、シフトJISコードに設定する。 if ( ( $ref_test =~ tr/\?/\?/ ) > 2 ) { $type = "shiftjis"; } } return $type; # 戻り値の設定 }
httpdのログファイル(referer)より、アクセス元のURIの解析を行なう場合
「セキュリティと応答速度を意識したWWWサーバー(AN HTTPD)設定例の項14.refererファイルの解析Perlスクリプト」などを使用してアクセス元のURI(検索キーワードなども含まれている)がわかる場合は、上記のスクリプトで、refererの日本語化を行なう必要はありません。この日本語化の処理は、サーバーに負担をかけますので、思い切って止めましょう。以下の様に該当行の3行をコメント行に変更するだけです。
# 文字コードライブラリ $jcode = '../report/jcode.pl'; は、 #$jcode = '../report/jcode.pl'; # <------- コメント行にします。 --------------------------------------------------------------------------------------- # シフトJIS変換(検索エンジンからのキーワード処理) #require $jcode; #&jcode'convert(*ref,'sjis'); の2行は、コメント文に変更します。
汎用的なCGIの無駄な処理回路を削除する。
多くの人々から支持され人気のあるCGIの多くは、汎用であり、できるだけ多くの人々が満足し、ある程度変更が容易なように巧みに工夫されています。また、どんなサーバーでも動作できるようにPerl4のスクリプトで記述されていたり、目には見えない部分で苦労されています。これらの汎用スクリプトが読みやすいということと、処理速度が早いということとは反比例すると私は考えています。出来ればということになりますが、極力スクリプトを解読し、無駄な処理を削除(改造)するようにしましょう。
ただし、あまり改造を行ないますと、CGIのバージョンが上がった時の保守が大変になります。従って、自分が保守できる範囲に限定することになります。場合にもよりますが、配布スクリプトの小変更のバージョンアップ(改版)などでは、その部分を変更してやる方が早いことが多いです。配布スクリプトの変更箇所が知りたい場合、「ファイル比較 phUpdate(Windows95/98/Me/ユーティリティ)」を使用すると変更箇所が一目瞭然となります。ですから、使用中の配布スクリプトは、ダウンロードした時点でファイル名にバージョン番号を付加して保管されることを強くお薦めします。
蛇足ですが、著作権表示は、最も重要な部分ですから、無駄な処理ではありません。絶対に削除してはいけません。
CGIのコメントを削除
一般的にフリーで配布されているCGIの多くは、perlという言語で書かれています。設置が容易で、どんなサーバーでも動作するように考慮されたCGIというのは、基本的にスクリプト中にコメントが多く書かれています。サーバーに負荷をかけるCGIを少しでも軽快にしたいですよね。
私は、perlで作成されたCGIしか使用していませんが、相当に気分的な速度向上策を行っています。それは、ソースから、コメント行を全て切り取ることです。スクリプトにもよりますが、大体、半分近くまで、ファイル容量を少なくすることができます。少なくともスクリプトファイルの読み込みが数ms単位で短縮できますし、実行前のperlの文法解析時間が若干短くなるはずです。体感できるかどうかは別の話ですけど。
なぜKENT WEBさんのCGIを多く使用しているのか?
今まで誰からもご質問を受けたことがないのですが、ここに書かせて頂きます。
色んなCGIの配布サイトもKENTさんと同じように素晴らしいと思いますが、開発がWindowsのAN HTTPDサーバーで行なわれているところが最も安心感があります。二番煎じや改造は簡単ですが、最初にスクリプトを配布された先達の方々には、尊敬し感謝の気持ちでいっぱいです。自宅サーバーを構築されている方は、これらのCGIスクリプトから、多くのことが学べるはずです。私のようなCGI初心者でもスクリプトの改造を通して、多くのことを勉強させて頂きました。私がスクリプトを改造する目的は、自宅サーバー用途に合わせた極めて需要の少ない用途への最適化です。スクリプト自体の素晴らしさを否定するものではありませんので、誤解されないよう切に申し添えます。