アクセスカウンター
【環境条件】
Eclipse 4.4(ルナ)
XAMPP 1.8.3
Perlは既にインストール済み
CGIを使用する時は、必ずApachを起動する
■リンクカウンター
これまでに勉強した 例題1~例題4 へのリンクが張られています。夫々のページへジャンプする度に link.cgi が介在して、夫々のページへのアクセス回数がカウントアップされた後に、夫々のページが表示されます。リンクカウンターの仕掛けは、link.cgi にによって実現されていますが、link.html を見ている人には、このような裏の仕掛けは見えません。
では、最初の画面「link.html」を表示させて見る。

→リンクが貼られた例題1~例題4が表示されたら、どれでも好きな所を選んで見る。
1)例題1

2)例題2

3)例題3

4)例題4

次にリンクカウンターが仕掛けられた閲覧記録画面「link.cgi」を起動する
閲覧記録画面
パス入力

閲覧記録

■スクリプト
リンクカウンターでは、2つのファイルを使う。一つは、リンクを貼ったlink.htmlと、リンクカウンターの仕掛けがあるlink.cgiである。
スクリプト「link.html」
1.<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
2.<html>
3.<head>
4.<meta http-equiv="content-type" content="text/html; charset=UTF-8">
5.<title>リンクのページ</title>
6.</head>
7.<body link="blue" vlink="blue">
8.<h1>リンクのページ</h1>
9.<ul>
10.<li><a href="link.cgi?url=html.cgi">例題1</a>
11.<li><a href="link.cgi?url=time.cgi">例題2</a>
12.<li><a href="link.cgi?url=dice.cgi">例題3</a>
13.<li><a href="link.cgi?url=env.cgi">例題4</a>
14.</ul>
15.</body>
16.</html>
スクリプト「link.cgi」
1.#!/usr/local/bin/perl
2.
3.$thisfile = 'link.cgi';
4.$datafile = 'link.dat';
5.$pass = '0123';#パスワード
6.
#---- 基本プログラム ----
7.&decode;
8.if ($in{'url'}) { &url; }
9.elsif (exists $in{'pw'}) { &admin; }
10.else { &main; }
11.exit;
12.
13.#---- フォームデータ取得 ----
14.sub decode {
15. if ($ENV{'REQUEST_METHOD'} eq 'POST') {
16. read(STDIN, $buf, $ENV{'CONTENT_LENGTH'});
17. } else {
18. $buf = $ENV{'QUERY_STRING'};
19. }
20. foreach (split(/&/, $buf)) {
21. ($key, $val) = split(/=/);
22. $val =~ tr/+/ /;
23. $val =~ s/%([0-9a-fA-F][0-9a-fA-F])/chr(hex($1))/eg;
24. $in{$key} = $val;
25. }
26.}
27.
28.#---- リンク先の計数と表示 ----
29.sub url {
30. $file = -e $datafile;
31. if ($file) {
32. open(IN, $datafile);
33. while (<IN>) {
34. chomp;
35. ($url, $count) = split(/,/);
36. if ($url eq $in{'url'}) { $count++; $flag = 1; }
37. push(@data, "$url,$count\n");
38. }
39. close(IN);
40. }
41. if ($flag != 1) { push(@data, "$in{'url'},1\n"); }
42. open(OUT, ">$datafile");
43. print OUT @data;
44. close(OUT);
45. if (!$file) { chmod(0666, $datafile); }
46. # リンク先を表示
47. print "location: $in{'url'}\n\n";
48.}
49.
50.#---- 管理画面への入り口 ----
51.sub main {
52. &html('この先は管理人しかは入れません');
53. print <<END;
54.<form action="$thisfile" method="post">
55.パスワード <input type="password" size="10" name="pw">
56.<input type="submit" value="管理画面へ">\n</form>
57.</body>\n</html>
58.END
59.}
60.
61.#---- 管理画面 ----
62.sub admin {
63. if (!$in{'pw'}) {
64. $s1 = 'パスワードが入力されていません';
65. } elsif ($in{'pw'} ne $pass) {
66. $s1 = 'パスワードが違います';
67. } elsif (! -e $datafile) {
68. $s1 = '閲覧記録はありません';
69. } elsif ($in{'del'}) {
70. unlink $datafile;
71. $s1 = '閲覧記録を削除しました';
72. }
73. if ($s1) {
74. &html($s1);
75. } else {
76. open(IN, $datafile);
77. @data = <IN>;
78. close(IN);
79. &html('閲覧記録');
80. print "<table border="1">\n";
81. print "<tr><th>URL</th><th>回数</th></tr>\n";
82. foreach (sort @data) {
83. chomp;
84. ($url, $count) = split(/,/);
85. print "<tr><td>$url</td><th>$count</th></tr>\n";
86. $total += $count;
87. print <<END;
88.<tr><th>Total</th><th>$total</th></tr>\</table>
89.<form action="$thisfile" method="post">
90.<input type="hidden" name="pw" value="$pass">
91.<input type="hidden" name="del" value="1">
92.<input type="submit" value="記録削除">\n</form>
93.END
94. }
95. }
96. print <<END;
97.<form action="$thisfile">
98.<input type="submit" value=" 戻る ">\n</form>
99.</body>\n</html>
100.END
101.}
102.
103.#---- HTML 書き出し ----
104.sub html {
105. print <<END;
106.Content-type: text/html\n
107.<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
108.<html>\n<head>
109.<meta content="text/html;">
110.<title>閲覧記録管理</title>
111.</head>\n<body>
112.<h1>閲覧記録管理</h1>
113.<p><b>$_[0]</b></p>
114.END
115.}
●●● 解説 ●●●
1)スクリプト「link.html」
■ リンクデータ
先ず、link.html の中でどのようにリンクデータをlink.cgiへ送られるか?
→10~13行目の
2)スクリプト「link.cgi」
■ リンクカウンタの動作原理
1)link.html の中で、"link.cgi?url=html.cgi" としてリンクが張られ、link.cgi url=html.cgi というデータが送られてきたときの link.cgi での処理を見てみよう。
基本プログラムの7行目の&decodeで、
decodeサブルーチンを呼び出し、link.htmlから送られてきたデータをハッシュ %in に格納する。今回は、キー url の値 $in{'url'} が 'html.cgi' という文字列としてセットされる。
基本プログラムの8行目のif ($in{'url'}){&url;}で、キー値が真に成り、urlサブルーチンが呼び出される。
【urlサブルーチン】
28.#---- リンク先の計数と表示 ----
29.sub url {
30. $file = -e $datafile;#ファイルテスト演算子
31. if ($file) {
32. open(IN, $datafile);
33. while (<IN>) {
34. chomp;
35. ($url, $count) = split(/,/);
36. if ($url eq $in{'url'}) { $count++; $flag = 1; }
37. push(@data, "$url,$count\n");
38. }
39. close(IN);
40. }
41. if ($flag != 1) { push(@data, "$in{'url'},1\n"); }
42. open(OUT, ">$datafile");
43. print OUT @data;
44. close(OUT);
45. if (!$file) { chmod(0666, $datafile); }
46. # リンク先を表示
47. print "location: $in{'url'}\n\n";
48.}
urlサブルーチンの処理は、
変数$datafile に代入した html.cgi へのリンク回数を 1 だけ加算して書き直すための処理。
$datafile には、リンクされた順に 「リンク先」 と 「リンク回数」 をコンマで区切って、次のようにリンク先毎に1行に記録することに決めてあります。

33~38行目の while ループでは
$_ に各行が読み出されます。$_ の最後にある改行文字を chomp 関数で取り除き、split 関数 で $_ を区切り文字 「, 」 で分割して $url と $count に取り出しています。
37行目の push 関数 は、
第1引数で指定した配列の末尾に、第2引数で指定した値を付け加える関数です。結局、この33~38行目の while ループでは、36行目でアクセス回数をインクレメントすることにより、ファイルの内容を書き換えるためのデータを配列 @data として準備したことになります。
41行目の処理は、
初めてのリンク先は $datafile の中に該当するデータが無いため、このリンク先をデータとして追加する処理です。
42~44行目の処理は、
@data を $datafile に書き込み、$datafile を新規に作成したときには、chmod 関数 で パーミッションを 666 に変更しています。
47行目の print "location: $in{'url'}\n\n"; は、
サーバーに対して 「この URL を表示してください」 と指示しています。この記述で改行記号が2つ付いていることに注意してください。CGI ヘッダーの出力と同様に2行目を空行にして出力しなければなりません。
■ 管理画面への入り口
「link.cgi」を動作させると、基本プログラムより、「管理画面」 への入り口が表示されます。
3.$thisfile = 'link.cgi';
4.$datafile = 'link.dat';
5.$pass = '0123';#パスワード
#---- 基本プログラム ----
7.&decode;
8.if ($in{'url'}) { &url; }
9.elsif (exists $in{'pw'}) { &admin; }
10.else { &main; }
11.exit;
12.
5行目に $pass として、管理者用のパスワードを設定しています。'Open Sesame' と設定しようと、'開けゴマ' と指定しようと、任意の文字列を設定します。
→link.cgi が単純に起動されれば、url=xxx とか pw=xxx というようなデータは送られてきてはいないため、main サブルーチンが呼び出されます。
50.#---- 管理画面への入り口 ----
51.sub main {
52. &html('この先は管理人しかは入れません');
53. print <<END;
54.<form action="$thisfile" method="post">
55.パスワード <input type="password" size="10" name="pw">
56.<input type="submit" value="管理画面へ">\n</form>
57.</body>\n</html>
58.END
59.}
52行目で html サブルーチンを呼び出し、'この先は管理人しかは入れません' というメッセージを表示する HTML の前半を出力し、ヒアドキュメントで 54~57行目のフォームを出力しています。このフォームで、パスワードの入力枠と 「管理画面へ」 という submit ボタンが作られます
■ 管理画面
「管理画面」 を表示しているのは、この admin サブルーチンです。このサブルーチンは、9行目で (exists $in{'pw'}) が真の場合に呼び出されます。即ち、exists 関数を使って、ハッシュ %in の中に pw というキーが存在するかどうかを調べ、存在する場合にのみこのサブルーチンが呼び出されるようになっています。この管理画面では、「閲覧回数の集計」 を見たり、「閲覧記録をクリア」 することもできるようにしています
61.#---- 管理画面 ----
62.sub admin {
63. if (!$in{'pw'}) {
64. $s1 = 'パスワードが入力されていません';
65. } elsif ($in{'pw'} ne $pass) {
66. $s1 = 'パスワードが違います';
67. } elsif (! -e $datafile) {
68. $s1 = '閲覧記録はありません';
69. } elsif ($in{'del'}) {
70. unlink $datafile;
71. $s1 = '閲覧記録を削除しました';
72. }
73. if ($s1) {
74. &html($s1);←htmlサブルーチンの呼び出し
75. } else {
76. open(IN, $datafile);
77. @data = <IN>;
78. close(IN);
79. &html('閲覧記録');←htmlサブルーチンの呼び出し
80. print "<table border="1">\n";
81. print "<tr><th>URL</th><th>回数</th></tr>\n";
82. foreach (sort @data) {
83. chomp;
84. ($url, $count) = split(/,/);
85. print "<tr><td>$url</td><th>$count</th></tr>\n";
86. $total += $count;
87. print <<END;
88.<tr><th>Total</th><th>$total</th></tr>\</table>
89.<form action="$thisfile" method="post">
90.<input type="hidden" name="pw" value="$pass">
91.<input type="hidden" name="del" value="1">
92.<input type="submit" value="記録削除">\n</form>
93.END
94. }
95. }
96. print <<END;
97.<form action="$thisfile">
98.<input type="submit" value=" 戻る ">\n</form>
99.</body>\n</html>
100.END
101.}
63行目の (! $in{'pw'}) は、キー pw の値が空白なら真となる条件式です。空白は偽と見做されます。これに否定演算子 ! を付けて、「空白なら」 という条件式にしています。
67行目でも否定演算子 ! を使っています。-e はファイルの存在を調べるファイルテスト演算子で、ファイルが存在すれば真を返します。これを否定して、「ファイルが無ければ」 という条件式にしています。
67行目の条件式は、キー del の値が真ならば、という式です。「記録削除」 ボタンが押されると、del=1 というデータが送られるようにしてあるため、このデータを受けたときには unlink 関数で $datafile を削除してしまいます。
63~72行目の何れかの条件が真の場合には、変数 $s1 にメッセージをセットします。従って、73行目の条件式が真となり、html サブルーチンで HTML の前半を出力して、96行目以降の処理を行って終了します。
【htmlサブルーチン】
103.#---- HTML 書き出し ----
104.sub html {
105. print <<END;
106.Content-type: text/html\n
107.<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
108.<html>\n<head>
109.<meta content="text/html;">
110.<title>閲覧記録管理</title>
111.</head>\n<body>
112.<h1>閲覧記録管理</h1>
#----引数表示----
113.<p><b>$_[0]</b></p>
114.END
115.}
上記以外の場合には、76行目以降の処理を行います。先ず、76~78行目でデータを @data に読み取り、html サブルーチンで HTML の前半を出力します。80~88行目はデータを表の形で出力しています。出力に際しては、URL をアルファベット順にソートして(82行目の sort 関数による)、合計回数を計算して(86行目)表の最後に書いています。
89~92行目の出力で、「記録削除」 ボタンのあるフォームをを作っています。このフォームには、pw と del のコントロールを隠しておきます。
96~100行目のヒアドキュメントによる出力は、「管理画面への入り口」 に戻すボタンを作っています。「戻す」 ボタンを押したときには、単純に $thisfile(link.cgi)にリンクを張ったときと同様の動作となり、次には main サブルーチンが呼び出されることになります。
※コメント投稿者のブログIDはブログ作成者のみに通知されます