めざせ、HTMLの鉄人!

「HTML教室」(CGI篇・その1)

次のページ

CGI とは Common Gateway Interface の略で、データベースにアクセスしブラウザに表示させるためにサーバーに置くプログラムのことを指します。多くは perl というプログラム言語で書きます。perl はどの Linux ディストリビューションには標準でついてきますし、入門書も多いので勉強しやすいと言われています。perl はスクリプトを書くだけで、コンパイルなしでプログラムを動かすことができ、プラットフォームを越えて使うことができるので人気があります。

私は perl はほんの少しかじっただけですので、詳しい解説はできませんが、ゲストブックを例題に、CGI の実際をレポートしたいと思います。


1 guest.html

私のゲストブックの HTML ソース(guest.html)は次の通りです。

<HTML>
<HEAD>
   <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=EUC-JP">
   <TITLE>Penguin Club-Guest Book
</TITLE>
</HEAD>
<BODY BACKGROUND="./images/crc-back.gif">
<CENTER><IMG SRC="./images/penguin_club.gif" HEIGHT=60 WIDTH=292></CENTER>
<UL>
<UL>
<center>
<h2>Guest Book</h2>
<P><A href="guestsee.cgi">ゲストブックを見る</A>   <A href="index.html">ホームページに戻る</A></P>
<blockquote>
Penguin Club においでいただきありがとうございました。よろしかったら、コメントをお残しください。<BR>
</blockquote>
<HR>
<FORM action="guestadd.cgi" method="POST">
<table>
<tr><td>お名前</td><td><INPUT name="myname" size="40"></td></tr>
<tr><td>メールアドレス</td><td><INPUT name="mymail" size="40"></td></tr>
<tr><td colspan="2">コメントをお願いします(200文字以内−このボックス一杯に書けます)<BR>
<TEXTAREA name="mycomment" rows="4" cols="50"></TEXTAREA><br><br>
<center><INPUT type="submit" value=" 書 込 "> <INPUT type="reset" value=" 取 消 "></center>
</table>
</FORM>
</center>
<HR>
</BODY>
</HTML>
ゲストブックに書き込んでもらいたいのは「名前」「メールアドレス」「コメント」の三つにし、それぞれ myname mymail mycomment という名前をつけました。myname と mymail は 一行だけのボックスで 40字にしました。コメントは 50字 × 4行にし、スクロールなしで 200文字としました。
<tr><td>お名前</td><td><INPUT name="myname" size="40"></td></tr>
<tr><td>メールアドレス</td><td><INPUT name="mymail" size="40"></td></tr>
<tr><td colspan="2">コメントをお願いします(200文字以内−このボックス一杯に書けます)<BR>
<TEXTAREA name="mycomment" rows="4" cols="50"></TEXTAREA><br><br>
これらのデータを submit ボタンを押すと guestadd.cgi で処理するよう、次のように action を定義しました。
<FORM action="guestadd.cgi" method="POST">


2 guestadd.cgi

guestadd.cgi は perl で書きます。ゲストブックのデータは書き込みの時点でフォーマットを整えておくようにし、それをプレインテキスト形式で guest.txt というデータファイルに保存するようにしました。この方がエントリーの表示を早くできるからです。

次は guestadd.cgi のメインルーチンです。

#!/usr/bin/perl

# 漢字ライブラリの設定
require "jcode.pl";

# 初期化
&init_form('euc');

# ヘッダの送出
print "Content-type: text/html\n\n";

# guest.html からの情報
$myname     = $form{'myname'};
$mymail     = $form{'mymail'};
$mycomment  = $form{'mycomment'};
$datestr    = &get_date_string;

# 名前が入力されているかどうかの確認
if ($myname eq '') {
    &print_error("名前が入力されていません。");
    }

# メールアドレスが入力されているかどうかの確認
if ($mymail eq '') {
    &print_error("メールアドレスが入力されていません。");
    }

# メールアドレスが正しいかどうかの確認
if ($mymail !~ '@') {
    &print_error("メールアドレスが正確ではありません。");
    }

# コメントの長さをチェック
if (length($mycomment) > 500) {
    &print_error("コメントが制限字数を越えています。");
    }

# タグを置換
$mycomment =~ s/</</g;
$myname =~ s/</</g;

# データファイルのオープン
if (!open(TXT, "+<guest.txt")) {
    &print_error("ゲストブックのデータファイルが見つかりません。");
    }

# データファイルのロック
if (!&lock_file(TXT)) {
    close(TXT);
    &print_error("書き込みの衝突が起きました。");
    }

# これまでのデータファイルを全部読む
@txt = <TXT>;

# ファイルの先頭にまき戻す
seek(TXT, 0, 0);

# 新規データの書き込み
print TXT "<font color=\"#52188B\">■ <A HREF=mailto:$mymail>$myname</A>\n";
print TXT " [$datestr]</font><br>\n";
if ($mycomment ne '') {
    print TXT "$mycomment<br>\n";
    }
print TXT "<HR>\n";

# これまでのデータを書き加える
print TXT @txt;

# ファイルサイズを切りつめる
truncate(TXT, tell(TXT));

# データファイルのアンロック
&unlock_file(TXT);

# データファイルのクローズ
close(TXT);

# 書き込み成功
&page_top("ゲストブックへの書き込みありがとうございました");
&page_end;

# 終了
exit(0);

ここで中心的な部分は、

$myname     = $form{'myname'};
$mymail     = $form{'mymail'};
$mycomment  = $form{'mycomment'};
$datestr    = &get_date_string;
print TXT "<font color=\"#52188B\">■ <A HREF=mailto:$mymail>$myname</A>\n";
print TXT " [$datestr]</font><br>\n";
if ($mycomment ne '') {
    print TXT "$mycomment<br>\n";
    }
です。guest.html から発信された myname mymail my comment を受け取って、guest.txt に書きこんでいます。書き込み時間も自動的に記入するように &get_date_string というサブルーチンも使っています。


3 guestadd.cgi メインルーチンの解説

次にメインルーチンをすこしづつ解説します。

#!/usr/bin/perl
これはこのファイルを「perl で動かせ」という命令です。guestadd.cgi はプログラムファイルですから、このファイルを作成したら、実行属性をつけなければなりません。
# 漢字ライブラリの設定
require "jcode.pl";
perl で日本語を扱うためには jcode.pl が必要です。jcode.pl は適当なダウンロードサイトから手に入れておきます。
# 初期化
&init_form('euc');
これは次のサブルーチンで処理させます。&init_form の引数 'euc' は文字コードで、サブルーチンでは $charcode に価を引き渡しています。
sub init_form {
    local($query, @assocarray, $assoc, $property, $value, $charcode, $method);
    $charcode = $_[0];
    $method = $ENV{'REQUEST_METHOD'};
    $method =~ tr/A-Z/a-z/;
    if ($method eq 'post') {
        read(STDIN, $query, $ENV{'CONTENT_LENGTH'});
        }
    else {
        $query = $ENV{'QUERY_STRING'};
        }
    @assocarray = split(/&/, $query);
    foreach $assoc (@assocarray) {
        ($property, $value) = split(/=/, $assoc);
        $value =~ tr/+/ /;
        $value =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack("C", hex($1))/eg;
        &jcode'convert(*value, $charcode);
        $form{$property} = $value;
        }
    }
次は、ブラウザに文字列を送るためのきまり文句です。
# ヘッダの送出
print "Content-type: text/html\n\n";
guest.html からの情報 $myname $maymail $mycomment は次のようにしてチェックをかけています。コメントに < や > などが含まれているとブラウザに正しく表示されませんので、それらを取り除く作業もここでしています。エラーが出たら &print_error に表示させるようにしてあります。
# 名前が入力されているかどうかの確認
if ($myname eq '') {
    &print_error("名前が入力されていません。");
    }

# メールアドレスが入力されているかどうかの確認
if ($mymail eq '') {
    &print_error("メールアドレスが入力されていません。");
    }

# メールアドレスが正しいかどうかの確認
if ($mymail !~ '@') {
    &print_error("メールアドレスが正確ではありません。");
    }

# コメントの長さをチェック
if (length($mycomment) > 500) {
    &print_error("コメントが制限字数を越えています。");
    }

# タグを置換
$mycomment =~ s/</</g;
$myname =~ s/</</g;
さて、データファイルのオープンですが、ここでは読み書き両方できるようにオープンします。
# データファイルのオープン
if (!open(TXT, "+<guest.txt")) {
    &print_error("ゲストブックのデータファイルが見つかりません。");
    }
データファイルには同時に何人かがアクセスしようとしますので、それを避けるためにロックをかけます。ロックをかけられなかった場合はエラーを表示し、やりなおしてもらいます。
                                             
# データファイルのロック
if (!&lock_file(TXT)) {
    close(TXT);
    &print_error("書き込みの衝突が起きました。");
    }
ゲストブックでは新しいデータを先頭に載せるため、まずデータ全部を読みこんで、それから書き込みポイントを先頭に持ってきます。それが次のラインです。
# これまでのデータファイルを全部読む
@txt = <TXT>;

# ファイルの先頭にまき戻す
seek(TXT, 0, 0);
新規データを書き込んだら、これまでのデータを付け加え、ファイルの不要な部分を切り捨て、ファイルのロックを外してから、閉じます。次がその一連の作業部分です。
# これまでのデータを書き加える
print TXT @txt;

# ファイルサイズを切りつめる
truncate(TXT, tell(TXT));

# データファイルのアンロック
&unlock_file(TXT);

# データファイルのクローズ
close(TXT);
書き込みが成功したら、それを伝えたほうが親切なので、次のようなメッセージを表示して終了するようにしました。
# 書き込み成功
&page_top("ゲストブックへの書き込みありがとうございました");
&page_end;

# 終了
exit(0);


4 guestadd.cgi のサブルーチン

次は表示関係のサブルーチンです。

# エラー表示
# &print_error("メッセージ");
sub print_error {
    local($msg) = @_;
    &page_begin($msg);
    print "ブラウザの「戻る」アイコンを押してもとに戻り、";
    print "記入、または訂正してください。うまくいかない時は";
    print "<A HREF=\"mailto:webmaster\@penguinclub.net\">webmaster\@penguinclub.net</A>";
    print " までお知らせください。\n";
    &page_end;
    exit(0);
    }

# ページのはじめ
# &page_top("メッセージ");
sub page_begin {
    local ($msg) = @_;
    print "<HTML>\n";
    print "<HEAD>\n";
    print "<TITLE>$msg</TITLE>\n";
    print "</HEAD>\n";
    print "<BODY>";
    print "<H1>$msg</H1>\n";
    }

# ページの終わり
sub page_end {
    print "<HR>\n";
    print "<A HREF=\"guestsee.cgi\">ゲストブックを見る</A>\n";
    print "  ";
    print "<A HREF=\"http://localhost/~penguin/guest.html\">ゲストブックに記入する</A>\n";
    print "  ";
    print "<A HREF=\"http://localhost/~penguin/index.html\">ホームページに戻る</A>\n";
    print "<HR>\n";
    print "</BODY>\n";
    print "</HTML>\n";
    }
次は時刻を得てそれを文字列にするサブルーチンです。9/11/01 8:05AM のように表示させるようにしました。
# 現在日時を文字列にする
sub get_date_string {
    local($sec, $min, $hour, $day, $mon, $year);
    ( $sec, $min, $hour, $day, $mon, $year ) = localtime(time);

    $year -= 100;
    $mon++;
    $ampm = "AM";
    if ($hour == 12) {
        $ampm = "PM";
        }
    if ($hour > 12) {
        $hour -= 12;
        $ampm = "PM";
        }

    # 必要なら 0 を付加する
    if ($year < 10) {
        $year = "0$year";
        }10/2/01
    if ($min < 10) {
        $min  = "0$min";
        }
    return "$mon/$day/$year $hour:$min$ampm";
    }
データファイルのロックは次のようになっています。
# データファイルのロック
sub lock_file {
    local(*FILE) = @_;
    eval("flock(FILE, 2)"); # 2=LOCK_EX
    if ($@) {
        return 0;
        }
    return 1;
    }

# データファイルのアンロック
sub unlock_file {
    local(*FILE) = @_;
    eval("flock(FILE, 8)"); # 8=LOCK_UN
    }

上記のソースコードをメインルーチン、サブルーチンの順にならべて guestadd.cgi として保存すれば、CGI の出来上がりとなります。

次のページ



[INDEX]