<< 一行伝言板 >>

じゃぁ伝言板でも作ってみようかねぇ。うん。
さっそくメモ帳を起動しよう。それから、忘れないうちにファイルに名前をつけようね。
「dengon.cgi」とか。
ファイルを開いたり閉じたりする人は、とりあえず「dengon.txt」にしといて、
あとで「dengon.cgi」にするといいかもしれませんね。
じゃ、書き込みを始めましょう。

サンプルスクリプト(dengon.txt)はここ

#!/usr/local/bin/perl

この部分は、Perl(CGIを動かす言語)のありかを示すものです。
必ずCGIスクリプトの冒頭に記述されます。ないとCGIは動きません。
Pealのありかは、プロバイダで指定されてるはずです。
「#!」のあとに、Perlのありかを示しましょう。
ビワローブなら、このままで大丈夫です。
もしレンタルCGIをすでに使っている人ならば、そのファイルをメモ帳で開いて、
一行目を見てみるとよいでしょう。

# dengon v0.1 by 1999 Deihaz

「#」をつけると、その行のそれ以降がコメント(メモみたいなもの)になります。
つまり、プログラムはその部分を無視してくれます。
(「#!」は例外。省略不可。一行目の「#!」も厳密にはコメントですが、
それは絶対省略しないでね。)
この行は、伝言板の名前・バージョン・著作権を書いてるだけです。
ですからあなたがCGIスクリプト(CGIのプログラム)を作った場合は、
こんなふうにスクリプトの名前、作者の名前などを書いとくといいです。

require 'jcode.pl';

さて、本格的になってきました。
普通は、CGIに日本語でデータを送っても、日本語にはならないのです。
それをちゃんと日本語に直してくれるのが、「jcode.pl」というスクリプトです。
K.Utashiroさんという偉い人が作りました。
「require」という命令で、そのありがたいスクリプトが必要です、と宣言してるわけです。
命令とファイル名の間には半角スペースをいれましょう。
そしてファイル名「jcode.pl」は固有名詞みたいなものですから、
「'」(シングルクォート)で囲むわけですよ。
命令の最後には「;」(セミコロン)をつけましょう。
(「#」で始まるコメントには、セミコロンは必要ありませんが。)
じゃぁ、「jcode.pl」というファイルがいるじゃないか、と思った方。
すんませんでした。用意させていただきます。

jcode.txtです

「jcode.txt」を「jcode.pl」に名前変更して使って下され。
もちろん、「半角英数字しか使わんのじゃ〜」というフォリナーの方とかマニアックな方は
「jcode.pl」は必要ありません。

# 設定

$title = '一行伝言板';
$homepage = 'http://www.biwa.ne.jp/~fagott/';
$max = 10;

$method = 'POST';
$script = 'dengon.cgi';
$logfile = 'dengon.log';

# 設定終わり

ここは、いろんな準備をしているところです。
「$title」というのは、「『title』という名前の入れ物(変数という)」を意味しております。
「=」は、右のものを左の変数に入れる記号です。
つまり、「$title」という変数に、「一行伝言板」という文字を入れておくわけです。
以下同様に、いろんな変数にいろんな文字やら数値やらを入れてます。
ちなみに、変数の名前は大文字と小文字が区別されるのでご注意を。

$title:伝言板の名前
$homepage:ホームページのアドレス(伝言板から脱出するためのアドレス)
$max:メッセージを何行蓄えておくか。つまりこれだと10行
$method:データを送信する方法。くわしくはあとで
$script:このスクリプトのファイル名
$logfile:ログを保存しておくためのファイルのファイル名

$maxに入れる「10」は数値ですから、「'」では囲みません。
さて、気になるのは「POST」でしょう。
データをCGIに送る方法には「POST」「GET」の二種類があります。
「POST」だと、結構長いデータが送れます。
「GET」は、あまり長いデータは送ることが出来ませんが、CGIを呼び出すときに
「dengon.cgi?mode=form」などのように、直接データを記述できます。
まあ必要がない限り、「POST」を使っとくとこでしょう。

if ($ENV{'REQUEST_METHOD'} eq 'POST') {
	read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
}
else {
	$buffer = $ENV{'QUERY_STRING'};
}

ここはもうほとんどのCGIスクリプトでお決まりの部分なので、
説明を省略したいところですが、ある程度の説明はします。
先ほど、データ送信には二種類あると言いましたが、ここでは結局それを判断して
「$buffer」という変数に入れているわけです。
「POST」と「GET」では、データの送られ方が違うというわけですね。
この部分をてきとーに訳してみると、
>もし(if)、送信方法が「POST」ならば、
>標準入力から$bufferにデータの長さ分だけデータを入れる。
>そうでなければ(else)、
>データ列を$bufferに代入する。
・・・となります。ワケ分かりませんね。ワシも何言ってるかよく分かりません。

&decode;

簡単に言えば、「『decode』に飛ぶ」です。
「&」をつけると、そのあとに続く名前の場所に飛ぶというわけです。
じゃあ、「decode」ってどこにあるかというと、スクリプトの後ろの方にある
sub decode {・・・}
という部分です。こういうのをサブルーチンといって、
あるまとまった作業をするひとかたまりなわけです。
ちなみに「sub decode」では、送られてきたデータを使えるように変換するという
作業をしております。はい。後で説明します。
サブルーチンが終わると、またここに戻ってきます。

if (($form{'mode'} eq 'msg') and $form{'name'} and $form{'comm'}) {
	&regist;
	&viewlog;
}
else {
	&viewlog;
}

さて、そろそろ私も疲れてきましたが頑張っていきます。

「if」という命令はさっきも出てきましたが、
if (条件1) {処理A} elsif (条件2) {処理B} else {処理C}
という構文で、もし条件1が成立しているなら処理Aを実行し、
そうでなくとも、もし条件2が成立しているなら処理Bを実行し、
どれにもあてはまらなければ、処理Cを実行する、ということです。
elsifはたくさん並べられますし、elsifとかelseは省略も可能です。
この場合は、elsifが省略されてますね?

$form{'mode'}はまたあとで説明しますが、発言ボタンを押してこのスクリプトが
呼び出されたとき、$form{'mode'}には「msg」という文字が入っているはずなんです。
で、「eq」は、「文字が一致している」という意味なので、
発言ボタンを押してここに辿り着いたときにこの条件は成立します。
「and」は「なおかつ」という意味で、そのあとの$form{'name'}と$form{'comm'}は、
それぞれ「eq 何か」が省略されていて、つまりこれらをまとめると、
>モードが'msg'でなおかつ名前が何か入力されていてなおかつメッセージが何かあれば
ということになります。はあはあ、疲れた・・・。
つまり名前もメッセージもちゃんと入力しないとこの条件は成立しません。

条件が成立すると、「regist」に飛んでそのあと「viewlog」に飛びます。
そうでない場合は、「viewlog」に飛ぶだけです。

ちなみに「sub regist」では、メッセージをファイルに書き込み、
「sub viewlog」では、HTML(書き込みフォームとログ)を表示しています。
それぞれのサブルーチンについては、もちろん後ほど説明します。

sub viewlog {
	&header;
	print <<"EOH";
<font face="MS Pゴシック" size="3">
<p><font size="5" color="blue"><b>$title</b></font></p>
<p><font size="4"><b>何かの伝言に使って下さい。</b></font></p>
<form method="$method" action="$script">
<p><input type="hidden" name="mode" value="msg">
お名前:<input type="text" name="name" value="$form{'name'}"><br>
内容:<input type="text" name="comm" size="50"><br>
<input type="submit" value="発言/リロード"><input type="reset" value="クリア">
</p>
</form>
<hr>
EOH
	open LOG, "$logfile" or &error('Logfile cannot be opened');
		@lines = <LOG>;
	close LOG;
	foreach $line (@lines) {
		($date, $name, $comm) = split(/\"/, $line);
		print qq(<b>[$name]</b> $comm <small>\($date\)</small><br>\n);
		print qq(<hr>\n);
	}
	print qq(<a href="$homepage">トップへ戻る</a>\n);
	print qq(</font>\n</body>\n</html>\n);
}

さて、いよいよサブルーチンの解説です。
sub viewlog {・・・}は、前にも述べたとおり、サブルーチンの宣言です。
「&viewlog」から、ここに飛んでくるわけですね〜。
で、いきなりまた「&header」でsub headerに飛んぢまうのですが、
HTMLを書く準備をしに行きます(ついでに冒頭のタグの記述も)。

では、print <<"EOH";の解説をしましょう。
「print」は、主にHTMLを出力するのに使います。
「print 'おっす'」だと、「おっす」を出力するわけですね。
で、「<<」は、そのあとの文字(ここでは「EOH」)が出てくるまで、
文章(ここでは「<font・・・」から「・・・<hr>」までの11行分)を
出力し続けなさいという便利で恐ろしい記号です。
この機能を「ヒアドキュメント機能」というらしいです。

で、「EOH」を囲んでいるのが「'」ではなく「"」であることに注意。
「'」だとどうなるか・・・。ヒアドキュメント(EOHまでの文章)の2行目に、
「$title」がありますが、これがそのまま「$title」と表示されてしまうんです。
ところが、「"」で囲むと、「$title」の中身、この場合では「一行伝言板」を
ちゃんと表示してくれるんです。

同じことは、普通のprint文とか変数の代入でも出来ますよ。
$hensuu = 'なかみ';
としておいて、
print '$hensuu' → 「$hensuu」が出力される
print "$hensuu" → 「なかみ」が出力される
$abc = '$hensuu' → $abcには「$hensuu」という文字が入る
$abc = "$hensuu" → 「$abc」には「なかみ」が入る
というわけですな。

で、あとは「EOH」が出てくるまでダラダラとHTMLの記述がされています。
もちろん、このまま出力されるので、好きなようにいじることが出来ますよん。
主に、書き込みフォームの記述をしておりますね。
ヒアドキュメントの5行目に「<input type="hidden"・・・>」があるでしょう。
サブミットボタン(発言/リロードボタン)が押されると、
「mode」を「msg」にしろっちゅうことです。
そうすると、このスクリプトの最初の方(2回目のif文あたり)で説明した
「($form{'mode'} eq 'msg')」が成立するわけですな。おわかり?

はい。サクサクいきます。「open LOG, "$logfile"」というのは、
「LOG」というファイルハンドル(コードネームみたいなもん)で、
ファイル名が「"$logfile"」のファイルを扱いますよ、という宣言です。
ファイルハンドルは別になんでもいいですが、大文字が一般的のようです。
ファイル名は「"」で囲んでるので、もちろんここでは
「$logfile」の中身「dengon.log」がファイル名として参照されます。
「or」は、「open」がもし失敗したときに、そのあとを実行しろという事です。
この場合は、「sub error {・・・}」に「'Logfile cannot ・・・'」という文章を持って
飛んでいくわけです。

はい次。「@lines」ってなんじゃ。簡単に言うと変数の一種なんですが、
「$lines[0]」「$lines[1]」「$lines[2]」・・・の集まりです。配列変数といいます。
「<LOG>」は、ファイルハンドルが「LOG」のファイルの中身っちゅうことです。
つまり、「@lines = <LOG>;」で、ログファイルがそっくりそのまま「@lines」に
コピーされるんですねぇ。どういうふうに入るかというと、改行文字で区切って入ります。
つまり、ログファイルの中身がもし「あいうえお\nかきくけこ\nさしすせそ\n・・・」ならば、
「$lines[0] = 'あいうえお'」「$lines[1] = 'かきくけこ'」「$lines[2] = 'さしすせそ'」・・・
となります。ちなみに「\n」は改行文字のことです。
それがすんだら、「close」で「LOG」を扱うのを終了します。
扉といっしょ。あけたら閉じないとねぇ。

長いけどがんばってね。「foreach $line (@lines) {・・・}」は、
「@lines」の中身をひとつずつ取り出して、それをいちいち「$line」に入れて、
「{・・・}」の処理をしろ、という大変便利な命令です。
伝言を一件ずつ取り出してるわけです。
「split (/\"/, $line)」は、「$line」の内容を「\"」で分けるということです。
「\"」は、「"」の事なんですが、囲む記号「"」と区別するために「\」をつけます。
伝言ファイルは、日付と名前とコメントを「"」で区切ってるので、
「($date, $name, $comm) = split(/\"/, $line);」によって、$dateと$nameと$commに
それぞれ日付と名前とコメントが入ります。
さて。またまた出てきた「print」ですが、「print qq(・・・)」というのは、
実は「print "・・・"」とほとんど同じ機能です。唯一違う点は、
「print "・・・"」の中では「"」自体が使えないんです。
ヒアドキュメントは別ですが。
(ほんとはいちいち「\"」とすれば使えるけど。めんどくさいし。)
「print qq(・・・)」の中では「"」がそのまま使えるんですねぇ。
HTMLの記述では「"」を使うことが多いので、私は普段これを使ってます。
名前・コメント・日付をHTMLで表示して、「foreach」で繰り返します。
「\(」「\)」は、「(」「)」のことですが、
「print qq(・・・)」で使っているカッコと区別するために「\」をつけています。

はい。最後に「トップへ戻る」のリンクを記述して、
HTMLの終了タグなどを記述して、「sub viwlog」は幕を閉じます。

sub regist {
	($sec, $min, $hour, $mday, $mon, $year) = localtime(time);
	$mon++;
	if ($hour < 10) {
		$hour= "0$hour";
	}
	if ($min < 10) {
		$min = "0$min";
	}
	if ($sec < 10) {
		$sec = "0$sec";
	}
	$date = "$mon/$mday $hour:$min:$sec";
	$logname = "$form{'name'}";
	open LOG, "$logfile" or &error('Logfile cannot be opened');
	@lines = <LOG>;
	close LOG;
	@newlines = @lines;
	unshift (@newlines, "$date\"$logname\"$form{'comm'}\"$ENV{'REMOTE_HOST'}\n");
	if (@newlines > $max) {
		pop (@newlines);
	}
	open LOG, ">$logfile" or &error('Logfile cannot be written');
	eval 'flock(LOG, 2);';
	print LOG @newlines;
	close LOG;
}

「regist」サブルーチンです。メッセージをログファイルに書き込みます。

「localtime(time)」は、ローカルな(日本なら日本の)日付・時刻を表すものです。
大体分かると思いますが、「$mday」ってのは「何日」です。
「$mon++」なんですが、変数に「++」をつけると、それを1増やします。
なぜかっていうと、なぜか「月」だけは、1月が「0」2月が「1」・・・12月が「11」なんです。
仕方がないので、1増やしてるってワケ。

次のif文3つは、時刻を表示するときにケタをそろえてるだけです。
数値が10未満、つまり一ケタなら、あたまに「0」をつけます。
「1時1分1秒」のとき、「1:1:1」よりも「01:01:01」の方がそれっぽいでしょ?

あとしばらくは、いままでの説明で分かるのではないでしょうか。
「$date」に日付・時刻を入れ、「$logname」に名前を入れ、
ファイルを開いて「@lines」にログファイルを読み込み、ファイルを閉じます。
それでもって、「@newlines」に「@lines」の内容を移し変えます。

「unshift (@newlines, "・・・");」ですが、
「@newlines」の配列の先頭に、「"・・・"」を挿入してます。
ログファイルの一番最初に、最新のメッセージを挿入するわけですね。
日付と名前、コメント(ついでにリモートホスト)を「\"」つまり「"」で
区切って入れてます。
そして最後には「一件」を終了する印として、「\n」つまり「改行」を入れます。

次のif文では、ログの件数が「$max」(ここでは10件)を越えた場合に、
「pop」という命令で、「@newlines」のいちばんお尻の書き込みを削除しています。

そして次の「open」。よくみると、ファイル名の前に「>」がついてますね。
これは、「書き込みますよ〜」という合図です。
普通に「open」しただけでは、ファイルを「読み込む」ことしか出来ないんですね〜。
で、次の「eval・・・;」の一行ですが、これは、
「書き込むから、他のやつは書き込んだり読み込んだりするなっちゅうねん」
という命令です。
インターネットでは、同時に複数の人が同じファイルにアクセスする可能性があります。
もし書き込んでる最中のファイルに他の誰かがアクセスすると、そのファイルが
壊れてしまいます。だから、ちょっとアクセスを待ってもらうわけですね。
次の行。「print LOG ・・・」というふうに、print文のあとにファイルハンドルをつけると、
そのファイルに出力するということです。
ログファイルに、改めて「@newlines」の内容を書き込みます。
で、ファイルを閉じて、このサブルーチンも終了です。

sub header {
	print qq(Content-type: text/html\n\n);
	print qq(<html>\n<head>\n);
	print qq(<title>$title</title>\n</head>\n);
	print qq(<body text="black" bgcolor="#EEEEFF" link="blue" vlink="purple" alink="red">\n);
}

あいよっ。サブルーチン「header」です。
CGIでHTMLを出力する前にやっておかなければならないことがあります。
それが、「print 'Content-type: text/html\n\n';」です。
(私は好みで「print qq(・・・);」を使ってますけどね。)
最後は「\n\n」と、二回改行しないといけないんだそうです。
あとは、print文で機械的にHTMLの記述をしていけばいいだけです。
HPを手書きで(メモ帳で)作ってる人にはおなじみですね。
もちろん、ページの色とかは自由に変更してもらったらいいでしょう。

sub decode {
	@pairs = split(/&/, $buffer);
	foreach $pair (@pairs) {
		($name, $value) = split(/=/, $pair);
		$value =~ tr/+/ /;
		$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
		$value =~ s/\"//g;
		$value =~ s/<!--(.|\n)*-->//g;
		$value =~ s/<html(.|\n)*>//ig;
		$value =~ s/<body(.|\n)*>//ig;
		$value =~ s/<script(.|\n)*>//ig;
		$value =~ s/<table(.|\n)*>//ig;
		$value =~ s/<form(.|\n)*>//ig;
		$value =~ s/<img(.|\n)*>//ig;
		$value =~ s/\r\n//g;
		$value =~ s/\r|\n//g;

		&jcode'convert(*value, 'sjis');
		$form{$name} = $value;
	}
}

え〜と、「decode」サブルーチンです。
ここはお決まりの部分なので、あまり詳しくは説明しません。
フォームなどから送られてきたデータは、
「name=value&name=value&name=value&・・・&name=value」
という形で送られてきます。
それを分割して、使える形に直してるわけですな。

で、ぱっと見て分かると思いますが、HTMLタグっぽいものがあるでしょう?
これは、そのタグを禁止して消去してる部分なんです。
禁止したいタグをこのように書いておけば、そのタグは伝言板で使えなくなります。

「&jcode'convert・・・;」で、日本語を使えるようにデータを変換してます。
前に「require 'jcode.pl';」ってのがあったでしょう?
ここでそのスクリプトを実際に使ってるわけですね〜。いや〜ありがたい。

はい、「$form{$name} = $value」です。
これは「連想配列(ハッシュ)」といって、変数名の中にさらに変数名があるようなもんです。
つまりここでは、「$name」と「$value」を対応させているんですね。
$abc{'わし'} = 'ダンディ';
というふうにしておくと、
「$abc{'わし'}」の中身が「ダンディ」になるわけです。
ここでは、フォームデータで「mode=msg&name=大髪&comm=こんにちは!」が送られてきたら、
$form{'mode'} = 'msg';
$form{'name'} = '大髪';
$form{'comm'} = 'こんにちは!';
というふうになります。

sub error {
	&header;
	print qq(<font color=red><p>システムエラーが発生しました!</p>\n);
	print qq(<p><b>$_[0]</b></p></font>\n);
	print qq(</body>\n</html>\n);
	exit;
}

ようがんばった。最後です。「error」サブルーチン。
ファイルの読み書きなどでエラーが起こったらここに飛んできます。

「『$_[0]』ってなんじゃ?変数か??」と思ったでしょうか。
はい、エラーに飛ぶ処理のところで、
「・・・ &error('Logfile・・・')」
というふうに書きましたね。覚えてますか?
「&error」で飛ぶだけでなく、「'Logfile・・・'」という文字列も指定しました。
その文字は、「@_」という配列変数に入れられるのです。
だから、「$_[0]」には、その最初の文字列(っていっても1個しかないけど)が
入ってるわけです。ここでは「Logfile・・・」ですね。
こうすると、エラーを発生させるとともに、その内容も表示できるわけです。

最後の「exit」ですが、これは無理やりCGIを終わらせる命令です。
エラー出てんのに続けてても仕方がないですから。

さて、ログファイル「dengon.log」も作っておきましょう。
メモ帳などで空のファイルを作り、ファイル名を「dengon.log」に変更するだけです。
「dengon.cgi」をCGIサーバにアップするときに一緒にアップしましょ〜ね。

お疲れ様でした。理解しましたか?
これで簡単な伝言板なら自作できるはずですね?ね??
がんばってチョンマゲ。

戻る