戻る プロが教えるCGI作成のノウハウ 最後へ

解説0 | はじめに

CGIプログラミングは、実践で学んだノウハウの蓄積が大切です。
CGI、Perl、HTMLの3つの要素が複雑に絡み合っているので、より良いCGIプログラミングには、知識だけでなく、実践で学んだ数々のノウハウが欠かせません。
そこで、このページでは、僕自身が実践で学んだCGIプログラミングのノウハウを、できるかぎり公開していきます。すでにソースを公開しているCGI掲示板『Open!NOTES』を例にして、具体的にわかりやすく説明するつもりです。

ネットコンプレックス株式会社 CGI制作室 / YK

もくじ (最新更新 2000/4/21)
(1) どんな本でCGIを勉強したらいいのか?
(2) CGIプログラミングに必要な環境
(3) Perlの良い点、悪い点
(4) 正しいHTMLを書くには?
(5) プログラムを構造化する
(6) プログラムのスタイルを作る
(7) プログラムを作る手順
(8) 出力するHTMLをブロック化する
(9) フォームデータの取り込みを関数化する
(10) DBMに保存するデータを\0で連結するNEW
(11) 効率的な正規表現を書くには?
(12) CGIでメールを送信するときの日本語処理

Q&A用掲示板(最新更新 [an error occurred while processing this directive])
Q&Aのための掲示板も用意しました。なにかわからないことがありましたら、ご自由に質問をお書きください。ただし、仕事の合間に回答しますので、少し時間がかかるかもしれません。

[an error occurred while processing this directive]

なお、このページは、リンクフリーです。ご自由にリンクしていただいて結構です。


解説1 | どんな本でCGIを勉強したらいいのか?先頭へ

CGIプログラミングには、HTMLCGIPerlの3つの知識が必要です。
最近はCGI関連の入門書を買っていないので、けっこう古い本になりますが、下記の本がお勧めです。

* 『作ろう!魅せるホームページ 実践テクニックガイド』(インプレス)
CGIプログラミングには、まず、HTMLをエディターで直書きできるようになっていなければなりません。
僕は、この本で最初にHTMLの基礎を学びました。サンプル画像を使った実例も多くてわかりやすく、とりあえずホームページ作りに必要なツールはすべてこれ1冊で揃えられるのがよかったですね。といっても、僕が買ったのは旧版のほうなので、最近の新版の良し悪しは、よく店頭で確かめてからお買い求めください。

* 『CGIプログラミング入門』(翔泳社)
CGI全般の基礎を学ぶには、この本ですね。
最近は、もっと良い入門書もあるのかもしれませんが、CGIに必要な基礎から始めて、最低限必要な知識までをていねいに網羅しています。熟読モードで読みました。必携です。

* 『初めてのPerl』(オライリー&ソフトバンク)
CGIプログラムは、たいていPerl言語で書くことが多いので、Perlが使いこなせなければなりません。「CGIマスターへの道は、Perlマスターへの道だ!」と言っても過言ではないのです。
この本は、プログラミング初心者には難しいかな?とも思いますが、著者がプログラミングの楽しさを知ってるところがいいですね。たんにコマンドの説明をしているだけの入門書が多いので。当然、熟読モードで読みました。これも必携です。

解説2 | CGIプログラミングに必要な環境先頭へ

とりあえずCGIプログラミングを始めるだけなら、CGIが使えるプロバイダーEUCが扱えるテキストエディター、それにFTPソフトさえあればなんとかなるんですが、続けるにはちょっとつらいでしょうね。CGIプログラムを変更するたびにプロバイダーにアクセスしなければならないし、エラーの原因もわかりにくい。

そんなわけで、動作テスト用にWebサーバーソフトPerl言語を用意しておくと便利です。
初めのうちは、Windows上で動作するAN HTTP ServerPerl for Win32を組み合わせて使えばいいのですが、本格的に取り組むつもりなら、Linux+Apache+Perl5を組み合わせてWebサーバーを立ち上げたほうがいいでしょうね。現状では、プロバイダーの多くがUNIX系のOSを使い、WebサーバーソフトにはApacheを採用していることが多いので、これらの知識は欠かせません。
この他にも、HTMLチェッカーや動作テストのために各種ブラウザーなども揃えておくといいでしょう。

ちなみに、僕自身は、下記のような環境で開発しています。
* 動作テスト用Webサーバー ⇒ PC:COMPAQ DESKPRO 4/66i、OS:FreeBSD 2.2.5、Webサーバーソフト:Apache 1.2.4、CGI用言語:Perl5.004_01、エディター:Mule、ブラウザー:Lynx、HTMLチェッカー:Weblint
* 端末 ⇒ PC:Mebius PC-W100D、OS:Windows95、ブラウザー:IE4&NN4.5、エディター:WZ-EDITOR、FTPソフト:WS_FTP
* 動作テスト用 ⇒ 各種ブラウザー、各種PDA、WindowsNT、IIS、プロバイダー、レンタルサーバーなど

解説3 | Perlの良い点、悪い点先頭へ

Perlは、CGIプログラムに最もよく使われている言語です。
強力なテキスト処理、気軽に扱えるデータベースなど、たしかに、現状ではCGIプログラミングにいちばん適しているとは思うんですが、けっこうクセのある言語でもあるので、その良し悪しを意識しておかないと使いこなしにくいでしょう。

Perlの良い点
(1) ヒアドキュメントと変数展開
CGIプログラムには、HTMLを出力させる部分がかなり多いので、プログラム中にHTMLをそのまま埋め込めるヒアドキュメントは、本当にありがたいですね。さらに、変数展開を使って、HTML中に変数を直接埋め込むこともできます(↓下図参照)。正直、ヒアドキュメントと変数展開の使えない言語では、CGIプログラムは作りたくないほどです。上手に使って、読みやすいプログラムを心掛けてください。

ヒアドキュメントと変数展開を使った場合
print <<END;
<TD bgcolor="${noteHeadColor}" class="head">
${notes_name}${note_no} | ■${title} ${nameNEmail} ${writeTime}
</TD>
END

ヒアドキュメントと変数展開を使わなかった場合
printf("<TD bgcolor=\"%s\" class=\"head\">\n", $noteHeadColor);
printf("%s%s | ■%s %s %s\n", $notes_name, $note_no, $title, $nameNEmail $writeTime);
print "</TD>\n";

(2) 正規表現
正規表現は、『文字列が特定のパターンに合致しているかどうかを調べるための仕組み』なんですが、関連する機能と組み合わせると、本来なら数十行にもなるはずの文字列処理を、たったの1行で実行できてしまいます(↓下図参照)。文字列とテキストの処理が多いCGIプログラムでは、助かりますねー。ないと、えらいことです。ただし、正規表現の記述の仕方によっては、処理速度が雲泥の差になるので、効率的な記述を学ぶようにしてください。

本文中のhttp://とhttps://で始るURLをリンクにする
$contents
  =~ s!(https?://[^<\s\x80-\xFF]+)!<A href="$1" target="_blank">$1</A>!go;

本文中のメールアドレスをリンクにする
$contents
  =~ s!([^@\x80-\xFF\s<>]+\@[^@\x80-\xFF\s<>]+)!<A href="mailto:$1">$1</A>!go;
$contents =~ s/"(?:mailto:)+/"mailto:/go;

(3) 連想配列とDBM
連想配列自体は、添字に文字列を指定する配列で、『Perlの便利な機能のひとつ』といったところなんですが、DBMとリンクさせると、データベースをあたかも連想配列かのように扱えるので、便利ですね。ただ、最近の大手のプロバイダーは、DBMを禁止しているところが多いので、広く使ってもらいたいCGIプログラムでは使わないほうがいいでしょう。

Perlの悪い点
(1) 遅い!
もともとインタプリター系で遅いうえに、プロバイダー上で動作させると、なおさら遅く感じてしまいますね。処理の重そうな個所は、ベンチマークをして、できるだけ効率的なプログラムを工夫したり、処理が遅くてもエラーにならないように気を付けてください。

(2) 言語仕様がきっちりしていない
これは、Perlが悪いわけではありません、念のため。Perlは、プログラマーの使い方に合わせて、かなり柔軟な書き方ができるように設計されています。これは、熟達した腕の見せ所!でもあるんですが、注意しないと、いいかげんなプログラムを書いてしまいがちです。

(3) ソースが他人にも読める
これもPerlが悪いわけではないんですが、ソースが他人にも読まれると思うと、プログラムをきれいにするために、けっこう時間をかけてしまいますね。もともとプログラムはきれいに書いてるつもりなんですが、変数名や関数名の付け方にまでよく長考しています。

解説4 | 正しいHTMLを書くには先頭へ

CGIプログラムを自分ひとりで使うのなら、出力するHTMLにそう神経質にならなくてもいいでしょうが、広く他人にも使ってもらうつもりであれば、できるだけ正しいHTMLを記述するように心掛けてください。
正しいHTMLを書くには、下記の点に気を付けるといいでしょう。

* HTMLチェッカーを使う
まずは、Weblint 97HTML-lintといったHTMLチェッカーを使って、出力したHTMLの記述に間違いがないかを調べましょう。手軽なわりには、予想もしなかった間違いがけっこう見つかるものです。

* 各種のブラウザーで動作確認する
HTMLチェッカーは正常に通っても、他のブラウザーで表示させてみると、意図した通りには表示されない場合があります。特にCSSでは、いつもこれに泣かされています。できるだけ数多くのブラウザーで動作確認をしたほうがいいでしょう。僕の場合は、IEとNNの3.0以上の全バージョン、ザウルス、Lynxで動作確認をしています。

* HTML仕様書のDTDで確認する
HTMLの仕様書DTDには、HTMLの仕様が詳細に定義されています。普段はあまり必要ないのですが、HTMLの詳細な仕様を確認するときに重宝します。DTDを読むには、読み方を少し勉強しなければなりませんが、そう難しいものではないので、ぜひ身に付けておいてください。

解説5 | プログラムを構造化する先頭へ

構造化プログラミングは、プログラミングの基本です。
ところが、入門書などでも説明していないことが多いんですよね。そのせいなのか、他人が書いたCGIプログラムを読んでみると、スパゲッティ状態だったりすることがけっこうあります。僕自身は、Perlを学ぶ前から知っていたので、『Perlを使って実践で学んだノウハウ』というわけではないのですが、簡単に説明しておくことにしました。

構造化プログラミングとは、一言でいえば、『プログラムをブロック化すること』です。
人間が一度に把握できる情報量には限りがあるので、プログラムをわかりやすい単位にブロック化することで、より複雑なプログラムを的確に把握できるようにします。同時に、ブロック化した単位を、他のプログラムにも流用できるようになります。
イメージとしては、命令を1つずつ並べていくのではなく、命令をブロック単位にまとめて作っていく感じですね。大切なのは、自分のプログラムをさっと読んで、そのブロック構造がわかるようにすることです。
プログラムを構造化するには、とりあえず、下記の点に気を付けるようにすればいいでしょう。

* { ... }で囲われた行をきちんとインデントする
ご存知のように、if、while、for、subといった構文は、その中の命令を{ ... }で囲うようになっています。これは、これらの構文がプログラムの最も基本的なブロック構造だからです。
まずは、これらの基本的なブロック構造が一目でわかるように、{ ... }で囲われた行をきちんとインデント(=字下げ)するように心掛けてください。命令は、できるだけ1行に1つにします。こうすることで、どこからどこまでがブロックなのか、どのようにブロック同士が組み立てられているかが、わかりやすくなります(↓下図参照)。ただインデントすればいいというわけではなく、わかりやすく合理的なブロック構造になるように、ブロック構造の組み立て自体も工夫してください。

インデントから読み取れるブロック構造
sub retryFlock {
    my($fh, $mode) = @_;

    
for ($i = 0; $i < $retryFlock_num; $i++) {
    
    
if (flock($fh, $mode)) {
    return
}
    
else {
    sleep 1;
}
    
}

    &errorMessage('** 正常に登録できなかった可能性があります **');
    exit(1);
}

* 機能として独立できるブロックを関数化する
プログラムを作っていると、自然と『機能としてまとまっているブロック』ができるはずです。これを独立させ、関数化するように心掛けてください。
関数については、入門書にも説明されていますが、たんにサブルーチンにするのではなく、きちんと関数の引数と戻り値を定義し、関数の内部で使われる変数もできるだけローカル変数にすることが大切です。こうして関数をブラックボックス化することで、関数をあたかも自前の命令のように扱えるようになります。
たとえば、上図のretryFlock()関数は、『$fhで指定されたファイルを$modeで指定されたモードで$retryFlock_numで設定された回数だけflock()命令を1秒以内おきに繰り返し、それでもロックできなければエラーメッセージを表示する』といった関数なのですが、使うときには、そこまで考えることなくflock()命令の代用として気軽に使うことができます。

ただ、CGIプログラムの場合には、ゴリゴリと構造化すればいいというわけでもなく、出力するHTMLの流れに沿って書いたほうがわかりやすくなる面もありますね。このへんのバランスは、それぞれのスタイルの問題なので、上達しながら徐々に決めていってください。

解説6 | プログラムのスタイルを作る先頭へ

構造化プログラミングに慣れてきたら、今度は、自分なりのプログラムのスタイルを作り上げていくといいでしょう。自分のスタイルを作ることで、プログラムをより的確に把握できるようになります。といっても、必要というわけでもないのですが、自分なりにスタイルを工夫していくのも楽しいものです。
プログラムのスタイルには、とくに決まったルールはないので、自分にとっても他人にとってもわかりやすいスタイルをいろいろと工夫してみてください。僕も、いまでもスタイルは少しずつ変わっています。『初めてのPerl』などのオーソドックスなプログラムリストも参考になるはずです。
参考までに、僕自身が工夫しているスタイルのいくつかを紹介しておきましょう。

* インデントを4タブにする
インデントを何文字にするかは、いろいろな流派?があるんですが、僕はタブを4カラムに設定して使っています。仲間内では『4タブ』と呼んでいますが、もともとは僕がC言語を学んだときに『K&R』が4タブだったので、それにならったんですね。『初めてのPerl』も4タブなので、Perlでも4タブが一般的なのでしょう。実際、4タブが一番使いやすいと思いますね。インデントがあまり深くならないわりに、見やすいし。でも、社内の8タブ派の人間は、『エディターのデフォルトが8タブなのだから、8タブにしたほうが誰が見ても意図した通りに表示される』と言っております。

* 関数のコメントに引数と戻り値を書いておく
構造化プログラミングで関数化するようにお勧めしましたが、関数の先頭にコメントを書いておくと、よりわかりやすくなります。僕は、『その関数の簡単な説明、引数と戻り値、影響を受ける/与える外部変数』をコメントに書くようにしています。たとえば、解説5で取り上げたretryFlock()関数の先頭には、下図のようなコメントを付けています。
こうしたコメントを付けることで、わざわざ関数内部を調べなくても、コメントを読むだけで、その関数を使えるようになります。

retryFlck()関数に付けているコメント
### sub retryFlock() :flockを繰り返す
#
# (IN)
# $fh :ファイルハンドル
# $mode :1=共有ロック、2=排他的ロック、8=アンロック
# (GLOBAL)
# $retryFlock_num :flockを繰り返す回数
#
sub retryFlock {

* 変数名や関数名の付け方を工夫する
変数名や関数名の付け方も、けっこう工夫のしどころです。僕の場合には、変数名の後ろに『その変数に保存している値の性質を表わす文字』を付けるようにしています。たとえば、数値であれば_numを、最大値であれば_maxを、最小値であれば_minを、番号であれば_noを…、といった具合です。構造体や拡張子のようなものですが、変数名を見るだけで、どんな種類の変数かが一目でわかります。ハンガリアン記法のように先頭に付けたほうがいいのかもしれませんが、関連する変数を並べたときに揃わないのが、どうも好きになれないですね。

* 2行にまたがる行の折り返し方
1行が長くなって2行に分けなければならないときに、どこで行を折り返すかもけっこう気にしますね。前の行とのつながりがわかりやすく、しかもインデントとも区別できるようにしたい。僕の場合は、次の行の先頭に半角スペースを2つ付けています。=や=~などを1行目の後ろに付けるか2行目の先頭にするかも悩むところですが、いまは2行目の先頭にしています。2行目の先頭のほうが、さっとナナメ読みしたときにわかりやすいので。

* Perl独自の構文はできるだけ使わない
これは、僕の個人的なスタイルですね。PerlとC言語の両方を使っているので、どちらを使ってもあまり違和感がないように、できるだけPerl独自の構文は使わないようにしています。

解説8 | 出力するHTMLをブロック化する先頭へ

CGIプログラムが出力するHTMLは、詰めてブロック化したほうが処理が速くなります(↓下図参照)。
たんにHTMLを出力するだけのCGIプログラムなら、きれいにHTMLをインデントしてもいいのですが、出力したHTMLをCGIプログラムで読み込んだり再加工するのであれば、HTMLをブロック化したほうが処理が速くなります。

Open!NOTESの投稿部分のHTML
<A name="20" ></A>
<TABLE width="95%" border="0" cellspacing="0" class="post">
<TR><TD bgcolor="#CCFFFF" class="body">投稿20 | ■わんわんわん <A href="mailto:info@net
complex.co.jp">YK</A> 2000/1/19(水)13:53</TD><TD bgcolor="#CCFFFF" align="right" class
="body"><A href="#top">▲</A> <A href="http://www.netcomplex.co.jp/cgi-bin/ontcgi/onti
nput.cgi?no=101&re=20">返事</A>/ <A href="http://www.netcomplex.co.jp/cgi-bin/ontcgi/o
ntdel.cgi?no=101&re=20">削除</A></TD></TR>
<TR><TD bgcolor="#FFFFFF" colspan="2" class="foot"><SPAN>
<BR>にゃんにゃんにゃん
</SPAN><P align="right">( しかし、このシステムはとっても軽快 )</P></TD></TR>
</TABLE>
<HR width="95%" size="1">

ブロック化するメリットについて、もう少し詳しく説明しましょう。

* 行数が4分の1になれば速度は2倍速くなる
出力したHTMLファイルをCGIプログラムで読み込む場合、同じ内容のHTMLであっても、行数が少ないほうが処理が速くなります。以前テストしたベンチマークでは、行数を4分の1に圧縮すると、処理は2倍速くなりました。

* HTMLの行の先頭で内容が判別できる
かといって、HTMLをやみくもに詰め込めばいいというわけでもありません。CGIプログラムで検索しやすいように、ブロック単位でまとめたほうがいいでしょう。
正規表現を使ってHTMLファイルを検索する場合、できるだけ行の先頭の文字列で内容を判別できるようにしておくと、処理が速くなります。たとえば上図の例では、行の先頭が<Aで始まれば「投稿部分の開始」を意味し、<BR>で始まれば「投稿の本文」を意味し、<HRで始まれば「投稿部分の終了」を意味しています。こうしておけば、「投稿の本文」を見つけたいときには、<BR>で始まる行を見つければいいわけです。

* CGIプログラムの中でHTMLが区別しやすい
これは処理速度とは関係ないんですが、CGIプログラムをインデントし、さらにCGIプログラム中に書かれたHTMLまでもインデントすると、両方のインデントが混在してかえってわかりにくくなるような気がします。

解説9 | フォームデータの取り込みを関数化する先頭へ

他のCGIプログラムを調べてみると、フォームのデータを取り込むためにcgi-lib.plを組み込んでいることがけっこうありますね。翔泳社の『CGIプログラミング入門』でも使われていましたが、cgi-lib.plを組み込むと、CGI関連のさまざまの機能が使えるようになります。
ただ、フォームデータの取り込みにしか使わないのであれば、せっかくのcgi-lib.plの他の機能が無駄になるので、代わりにフォームデータの取り込み専用の関数を用意したほうが効率的です。僕は、フォームデータの取り込みには、たいてい下図の関数を使っています。

フォームデータの取り込み用関数
### sub parseFormData() :フォームデータを解析する
#
# (OUT)
#    %in  :フォームデータの内容
#
sub parseFormData {
    my($formData_buf, @formData);
    my($name, $value);

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

    @formData = split(/&/, $formData_buf);
    foreach (@formData) {
        ($name, $value) = split(/=/);
        $value =~ tr/+/ /;
        $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;

        &jcode'convert(*value, 'euc');

        $value =~ s/&/&amp;/go;
        $value =~ s/</&lt;/go;
        $value =~ s/>/&gt;/go;
        $value =~ s/"/&quot;/go;

        $in{$name} = $value;
    }
}

この関数の使い方は、cgi-lib.plとほとんど同じです。この&parseFormData()関数を呼べば、すべてのフォームデータが%in連想配列にnameの値を添字として格納されます。このとき、自動的に漢字コードはEUCに、&<>"といったキャラクターはそれぞれ&amp;&lt;&gt;&quot;に変換されるようになっています。

解説10 | DBMに保存するデータを\0で連結する先頭へ

解説3でも触れたように、DBMと連想配列をリンクすると、データベースを連想配列として扱えるようになります。便利な機能なのですが、連想配列として扱えるといっても、実際には、データベースから検索してデータを取り出しているので、それなりに処理に負担がかかっています。とくにプロバイダ上で動作させる場合には、データベースを検索する回数を減らす工夫が必要でしょう。

そこで、同時に扱うデータを連結して、1つのデータとして連想配列に保存するようにします。たとえば、Open!NOTESでは、投稿1件に関するデータ(投稿者名、E-mailアドレス、自己紹介/近況、タイトルなど)は、すべて連結して1つのデータとしてDBMに保存しています。こうしておくと、1回の検索で投稿1件に関するデータをまるごと取り出すことができます(下図参照)。

投稿に関するデータをすべて連結して保存する
$userName = 'YK';
$email = 'komaki@netcomplex.co.jp';
$writeTime = '2000/3/29(水)12:00';
$post_no = 10;
$profile = 'プロフィール';
$title = 'タイトル';

$DBMFILE{"${note_no}:${post_no}"}
  = join('"', $userName, $email, $writeTime, $post_no, $profile, $title);

YK"komaki@netcomplex.co.jp"2000/3/29(水)12:00"10"プロフィール"タイトル

連結に使うキャラクタも、けっこう重要です。
たとえば、安易に&を使って連結すると、連結するデータに&が混入していた場合には、元通りにsplit()関数で分割できなくなります。ですから、連結にはデータ中に現れないキャラクタを使うのが鉄則です。と言っても、この「データ中に現れないキャラクタ」というのがけっこう難問で、すぐに\0が思い浮んだら、あなたはPerlの達人のはずです。
\0であれば、データ中に現れることはまず考えられないので、連結に使うキャラクタとしては最適でしょう。

でも、僕自身は\0ではなく"を使って連結することが多いですね。理由としては、
(1) Perlのマニュアルには、文字列中に\0が使えるとは明記されていない
(2) \0は画面に表示されないため、どこで連結しているかがわかりにくい
(3) C言語では\0は文字列の終端を意味するので、連結に使うことに抵抗感がある
といった点が気になるからなんですが、cgi-lib.plでも\0を使って連結しているので、まず大丈夫でしょう。
"で連結した場合でも、データ中にある"は&parseData()関数を通せば&amp;に変換されるので、問題はありません。