***HPクリエイターのためのCGI講座 第44回***

4.1 簡単な掲示板

まず初めに、単純に書き込むだけの掲示板を作ってみましょう。
書き込む内容を入力するエリアと、書込みボタンしかありません。

単純ですが、掲示板の基本となります。
しっかりとマスターしてください。

<漢字コードの問題>
漢字コードは、厄介なことに、複数のコード種類が存在します。
HPの作成経験者であれば、シフトJISコードやJISコードといった言葉を聞いたことがあるでしょう。
同じページの中に、違った漢字コードが混在すると、文字化けの原因になります。
しかし、掲示板に書き込む人が、どのような文字コードを使うかは、こちらには分かりません。
文字コードを統一するために、書き込まれた漢字コードを判定し、必要であれば、文字コードを変換してやる必要があります。
ここでのサンプルプログラムで、漢字コードの変換処理も学びます。

<プログラムの説明>
require '../../../cgi/jcode.pl';
日本語コード変換プログラムの場所を指定

$lock_file = "../../../cgi/easy_bbs.lock";
多重アクセス禁止用ロックファイル名(ロック用ファイル名)

&in_para; # 入力パラメータの取得
入力パラメータ取得用サブルーチンをコール
入力パラメータを受け取り、デコードします。

if ($out ne ""){ # ボタンが押されて、このCGIが起動された
    &log_write; # ログファイルへメッセージの書込み
}
このCGIが起動される要因として、2種類あります。
1.オープニング時に呼び出される
    「サンプルはここ」と記述された「ここ」をクリックした場合
2.掲示板の書込みボタンを押した場合

書込みボタンが押された場合は、 $out に "書込み" の文字列が格納されています。
書込みボタンが押されていない場合は、 $out には何も格納されていません。
このCGIプログラムの起動要因を、 $out に文字列が設定されているか否かで判定しています。

ここからは、HTMLテキストをブラウザへ出力
print "Content-type: text/html\n\n";# HTML文をブラウザへ出力
print "<html>\n<head>\n";
print "<title>EasyBBS</title></head>\n";
print "<FORM method=\"POST\"action=\"easy_bbs.cgi\"><TEXTAREA rows=\"15\" cols=\"40\" name=\"msg\"></TEXTAREA><INPUT type=\"submit\" value=\"書 込 み\" name=\"out\"></FORM>\n";
FORMタグ
    methodはPOSTで、CGI(easy_bbs.cgi)を呼び出す
    テキスト入力領域の大きさは、40文字*15行
    CGIプログラムの受け取る、テキスト入力パラメータの名前はmsg
    書込みボタンの名前はout

print "<hr size=2>\n";
水平線の表示。

open(LOG,"<easy_log.txt") || &error(0); # ロググファイルの読込みモードでのオープン
ログファイル(発言内容が記述されている)を読み込みモードでオープンします。

@log_buff = <LOG>; # ログファイルから全て、読み出し

ログファイルの内容を一度に配列@log_buffへ読み込みます。
このように、記述すると、配列の各要素にファイルの中の文字列が一行ずつ読み込まれます。
log_buff[0]・・・・1行目の文字列
log_buff[1]・・・・2行目の文字列
log_buff[2]・・・・3行目の文字列
                :
                :

close(LOG);
ログファイルのクローズ(読み込みが終了したので)

foreach (@log_buff){ # $_へ1行ずつ取り出す

    print "$_\n"; # 取り出した1行を表示
    print "<hr size=2>\n"; #区切り用ラインの表示
}
foerchを使って@log_buffから、1要素(1行)ずつ $_ に取り出します。
ここでの1行は、1メッセージです。
取り出したメッセージを、ブラウザへ表示して、その後、hrタグを使って区切り用の水平線を出力します。

print "</html>\n";
exit;
プログラムの終了


ここからは、入力パラメータ取得サブルーチン
sub in_para {
    if ($ENV{'CONTENT_LENGTH'} > 500){
    METHODがPOSTなので、環境変数CONTENT_LENGTHに文字列が格納されています。
    その値をチェックし、500文字以上はエラーとします。
        &error(0); # 入力メッセージの最大長を500バイト(半角500文字)とする
    }
    read(STDIN, $in_buffer, $ENV{'CONTENT_LENGTH'});# POSTなのでパラメータを標準入力から読み出す
    標準入力STDINから、パラメータを取得します。

    @pairs = split(/&/,$in_buffer); # パラメータのペアを切り出す
    パラメータは & で区切って送られています。
    配列@pairsの各要素に & で区切った文字列を格納します。

    foreach $pair (@pairs) {
    $pairに配列@pairsの各要素を取り出す
        ($name, $value) = split(/=/, $pair); # パラメータを名前と値に分解
        各パラメータは 名前=値 の形をしています。
        それぞれを$nameと$valueへ格納します。
        $value =~ tr/+/ /; # コードのデコード
        変換規則に従って+はブランクに置換
        $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
        16進文字列を、数値に置換
        &jcode'convert(*value,'sjis'); # S-JIS変換
        得られた文字列を、S-JISに変換
        $value =~ s/\"/&quot;/g; # タグ文字の変換
        特殊文字の変換
        "(ダブルクォテーション)は&quot;に置換
        $value =~ s/</&lt;/g;
        < は&lt; に置換
        $value =~ s/>/&gt;/g;
        > は&gt; に置換
        if($name eq "msg"){ # 入力メッセージの処理
            現在のパラメータの処理がmsg(投稿記事)の場合
            $msg = $value;
            記事を$msgに格納します。

            現在のパラメータの処理がmsg(投稿記事)の場合は、改行コードを<br>に置換します。
            (改行コードには、次の3種類がある)
            $msg =~ s/\r\n/<br>/g; # リターンコードを<br>に置換
            $msg =~ s/\r/<br>/g; # "
            $msg =~ s/\n/<br>/g; # "
        }
        if($name eq "out"){ # ボタンが押された場合
            現在のパラメータの処理が書込みボタンの場合
            $outに文字列(書込み)を格納します。
            $out = $value; # ボタンに表示している文字が格納される今回の場合は「書込み」
        }
    }
}

ここからは、投稿記事のログファイルへの書込み処理
sub log_write {
    &lock; # ロック処理
    多重アクセス排他処理

    open(LOG,"<easy_log.txt") || &error(1); # ログファイルを読込みモードでオープン
    ログファイルを読み込みモードでオープンします。
    @log_buff = <LOG>; # 全メッセージの読み出し
    ログファイルの内容を@log_buffへ読み出します。
   close(LOG);
    ログファイルのクローズ
    if (10 <= @log_buff){
        ログファイルへの最大格納メッセージ数は10件とします。
        現在のログファイルに格納されているメッセージ数が10件の場合。
        pop(@log_buff); # メッセージが10を超えれば、一番古いメッセージを削除する
        一番古いメッセージを配列@log_buffから pop 命令を使って、削除します。
    }
    unshift (@log_buff,"$msg\n"); #入力メッセージをメッセージの最初に追加
    今、取り込んだメッセージを unshift 命令を使って配列@log_buffの先頭に格納するします

    open(LOG,">easy_log.txt") ||     &error(1); # ログファイルを書込みモードでオープン
    ログファイルを書込みモードでオープン
    print LOG @log_buff; # メッセージのログファイルへ書込み
    配列@log_buffをログファイルへ書き出します。
    close(LOG);
    ログファイルをクローズする
    if (-e $lock_file){
        unlink($lock_file); # ロックの解除
    }
}

sub lock{ # ロック処理
    $retry = 5;
    while (!symlink(".", $lock_file)){
        if (--$retry <= 0){
            &error(0);
        }
        sleep(1);
    }
}

sub error{ # エラー処理
    if((-e $lock_file) && ($_[0] != 0)){
        unlink($lock_file);
    }

    print "Content-type: text/html\n\n";
    print "<html>\n<body bgcolor=#FF0000>\n";
    print "<center><h3>ERROR</h3>\n";
    print "</html>\n";
    exit;
}


********************************************************************************
講師:ALK alk@arkland.co.jp
運営:アークランド(株) http://www.arkland.co.jp