CGIを動作させる環境が整ったところで、まずはCGIの基本的な動作に慣れておきましょう。
先ほどのtest.cgiを見てください。
#!C:/Perl/bin/perl print "Content-type: text/html\n\n"; print "<HTML>\n"; print "<BODY bgcolor=#ffffff text=#000000>\n"; print "Hello! CGI!!\n"; print "</BODY></HTML>\n"; exit; |
1行目はPerlでCGIを作る時のお約束事です。CGIを動作させるコンピューターのPerl処理プログラムがインストールされている場所を絶対パスで指定します。
ここではCGIをローカルサーバ(Apache)で動作させるので、そのマシンのドライブ名から始まっていますが、一般的なWWWサーバでは "#!/usr/local/bin/perl" や "#!/usr/bin/perl" である事が多いです。但し、WWWサーバにAnHttpdを使用している場合、WWWサーバ側にPerlのパス設定をするので1行目のパスを合わせる必要はありません。
それからPerlでは「#」の後に書かれたテキストは改行するまでコメントとみなされて無視される他、通常一つの処理を書く度に「;」を付けてやる必要がありますが、この行だけは例外なのでそれらとは区別して覚えましょう。
その後に続くprint文は、ここでは標準出力(STDOUT)に""(ダブルクォート)内のテキスト等を出力する命令(関数)で、基本的にはこうして書いたテキストがブラウザに表示されます。
CGIはWeb上で動作する以上、クライアントからリクエストがあれば必ずブラウザ上に表示できる形で何かを返してやる必要があり、もし何も返さずに処理を終了(exit)すると、結果を受け取れなかったブラウザはエラーと判断してしまい、サーバ内の処理自体は正しく行われていたとしても、クライアントのブラウザにはエラー画面が表示される事になります。
また、通常のHTMLファイルや画像ファイルは、サーバがデータを送信する際に情報の種類を示してくれますが、CGIからの出力に対しては基本的にどのような情報なのかを判断してくれません。従ってCGI内でそれらを明示する必要があります。
3行目の"Content-type"という記述がそれを意味しており、この文とデータ本体の間には必ず1行以上の空白行が必要なので、Perl言語における改行コードである「\n」を2つ後ろに付けています。
それからここでは「text/html」で出力内容がHTMLファイルである事を示していますが、その他、「text/plain」(只のテキストファイル),「image/jpeg」(jpeg画像)等様々な形式の情報をCGIで出力する事が出来たりもします。HTML以外の出力をしている例に画像カウンタがあります。
そして最後にexitです。プログラムは基本的に上の行から順に処理されていきますが、この命令を読み取った時点で処理を終了します。従って最低でも1行目のパスとContent-typeの出力、exitがあればとりあえずCGIとして動作すると言えます。
上の例では只のHTMLファイルと何ら変わりません。折角CGIを使うのですから何かそれらしい処理をさせてみましょう。
#!C:/Perl/bin/perl srand; $r = rand(10); if($r < 5){ $mes = "こんにちは"; }else{ $mes = "Hello!!"; } print "Content-type: text/html\n\n"; print "<HTML>\n"; print "<BODY bgcolor=#ffffff text=#000000>\n"; print "$mes\n"; print "</BODY></HTML>\n"; exit; |
乱数と条件式を使い、「こんにちは」か「Hello!!」のどちらかをランダムに表示させています。
srandは乱数を発生させるrand関数を使用する際に必要なものですが、とりあえず無視して構いません。randを使用する場合に最初に1度書いておけばよいとだけ覚えておいてください(というか通常は省略しても構わないのですが)。
次に$rという変数に0から10までの乱数を代入しています。
変数(スカラー変数)とはプログラム内で処理を行う際に、一時的に数字や文字(スカラー)を記憶しておく為のものです。
変数名は半角のアルファベット、数字、_(アンダーバー)を使った任意の文字列で、大文字と小文字は区別され、文字数の制限もありません。その変数にどのような情報が入っているかが分かるような名前にするのが一般的です。
また、この場合$rが10になる事はありません、0以上その値未満なので気を付けてください。
続いて条件式です。「if(条件式){〜}else{〜}」で一まとまりとなっており、ここでは先ほどの$rに5未満の数値が代入されていれば変数$mesに「こんにちは」を、そうでなければ「Hello!」を代入します。
変数に文字列を入れる場合はprint文と同様に""で囲う必要があります。''(シングルクォート)も使えますが、とりあえず必要ありません。
#!C:/Perl/bin/perl @mes = ("こんにちは","Hello!!"); srand; print "Content-type: text/html\n\n"; print "<HTML>\n"; print "<BODY bgcolor=#ffffff text=#000000>\n"; print "$mes[int(rand(2))]\n"; print "</BODY></HTML>\n"; exit; |
先ほどのものを配列を用いて処理した他、処理の順番を変えてみました。
配列とは複数の変数を一まとまりで扱うものです。上の様に()内に並んだスカラー一つ一つが「$配列名[配列番号]」で呼び出せる為、様々な処理に利用できます。
また、配列番号は0から始まるという事に注意してください。この場合$mes[0]が「こんにちは」、$mes[1]が「Hello!!」という事です。
また、配列名と変数名は同じでも別個に扱われます。
@mesと$mesがあっても@mesの中身は$mes[0]といった形で表される為当然と言えば当然なのですが、紛らわしいので極力避ける事をお勧めします。
それから変数のつもりで$mes[3] = 'xxx';という書き方をすると、@mesのその要素を書き換える事になります。
では処理内容を解説します。
まず配列@mesを定義し、srandを書いておきます。
そして出力の時に直接rand関数を書いて@mesの内のいずれかを取り出しています。print文のダブルクォート内にintやrandといった関数を書いていますが、配列の[]内であるため関数が解釈されます。
また、先ほどと少し違い、int関数を用いて乱数の小数点以下を切り捨て、必ず0か1になる様にしています。
配列@mesの要素が2つなので、明示的に0か1になるようにしているのですが、実際には切り捨てなくとも同じ様に動作はします。
#!C:/Perl/bin/perl $file = "count.txt"; open IN,"$file"; $count = <IN>; close IN; $count++; open OUT,">$file"; print OUT $count; close OUT; print "Content-type: text/html\n\n"; print "<HTML>\n"; print "<BODY bgcolor=#ffffff text=#000000>\n"; if($count == 1){ print "あなたが一番乗りです!\n"; }else{ print "あなたは$count番目の来訪者です。\n"; } print "</BODY></HTML>\n"; exit; |
ファイルの読み書きを行い、極簡単なカウンタにしてみました。"count.txt"というテキストファイルにはカウントした数字のみが書き込まれています。
ファイルの読み書きにはファイルハンドルと言うものを使用します。
ファイルハンドルは、Perlのプログラム中でファイルのデータを表す文字列で、変数同様その都度自由な名前を付けることが出来ますが、変数や配列とは違い頭に$,@といった記号を持たず、関数等と紛らわしくなる等の理由で大文字にしておく事をお勧めします。
動作内容は、まず呼び出すファイル名を変数で指定しておきます。ファイルを読む際に直接ファイル名を書いてもよいのですが、ファイル名に限らず色々な数値や文字列をこの様にプログラムの最初に変数にしておくと、後で変更するのがとても楽になります。
次の「open ファイルハンドル名,ファイル名;」でファイルを読み書きします。ファイル名の前に何もなければ読み込み、">ファイル名"なら書き込み用にファイル内のデータとファイルハンドルを結び付けます。
そしてファイルハンドルのデータを変数として取り出しています。ファイルハンドルはファイルの内容の1行を1要素とした配列のように振舞い、「変数 = <ファイルハンドル名>;」と書けばその変数にデータの先頭から1行分だけ渡し、「配列 = <ファイルハンドル名>;」と書けばファイルハンドルのデータ全てを1行1要素として配列に渡します。
開いたファイルの処理が終わったら「close ファイルハンドル名」でファイルを閉じます。
「close IN;」までで"count.txt"の中の数字を取り出したので、その数字に1を足します。
「$count++;」は「$count = $count + 1;」と同じ意味です。
そして再び"count.txt"を書き込み用に開き、増やした値をファイルに上書きします。
「print ファイルハンドル名 "データ";」と書く事で、print文はファイルハンドルに対して出力する事になります。
後はカウントアップ後の数値を出力すれば、呼び出す度にカウントアップする単純なカウンタの完成です。ここではカウントが1だった場合違うメッセージを表示する処理を行っています。
ここまではCGI内部で処理をして表示するだけでした。次はCGIに対し任意の情報を送信し、CGIを通して表示してみましょう。
これが出来ればCGIの基本は一通り出来たことになりますが、この内容の解説の前にCGIの環境変数について知っておく必要があります。
実はCGIに限らずどのようなWebページにアクセスした場合でも、そのページにアクセスしたブラウザの様々な情報をサーバに送信しており、それらを環境変数と呼んでいます。
もちろん名前などの個人情報を勝手に送ってしまう事はなく、使用しているブラウザの種類やバージョン、アクセスに使用しているホスト名などの情報に限られています。
#!C:/Perl/bin/perl @env = sort(keys(%ENV)); print "Content-type: text/html\n\n"; print "<HTML>\n"; print "<BODY bgcolor=#ffffff text=#000000>\n"; foreach $key(@env){ print "\$ENV{'$key'} = $ENV{$key}\n"; } print "</BODY></HTML>\n"; exit; |
環境変数のリストを表示するCGIです。CGI(Perl)の環境変数は%ENVという連想配列に格納されます。
連想配列は「%連想配列名」で表され、「$連想配列名{'添え字'} = "値";」の様に、添え字と値それぞれの文字列を組にして扱う配列です。
処理内容は%ENVの添え字(キー)を文字コード順に並べ変えて配列として取り出し、それを巡回して「$ENV{'添え字'} = 値」の形で順に表示させているのですが、詳しい関数の解説はここでは省きます。
ただ、ここで一つだけ注意しておきたいのが、foreachの後のprint文です。""の中にいくつかある「\」に注目してください。
print文の出力内容を""で囲うと中に書いた変数は中身を解釈して出力されますが、「$」や「@」などの特殊な記号や「"」そのものを出力したい場合は、その前に「\」を付けてやる必要があります。ここまでのprint文の中でタグの要素を""で囲わなかったのは、実はそれが面倒だったからだったりします(笑
ブラウザで上手く文字が表示されない(文字化けする)場合は前に「\」を付ければ大丈夫な事がよくありますが、改行コードである「\n」の様に「\」を付けると違う意味になってしまう文字もあります。これをメタ文字と言い、Perlスクリプトの中で様々な意味を持ちます。
それから「\」自身もメタ文字であるため、「\」そのものを表示する場合「\\」と書かなければなりません。「\$」や「\@」もある意味メタ文字と言えます。
また、print文に限らず対象文字列を''で囲うと、その中ではメタ文字の他、変数なども解釈されずそのまま適応されます。例えば上の例で「print '\$ENV{\'$key\'} = $ENV{$key}\n';」と書くと、$keyや\n等もそのままになります。但し''の中で「'」を表示したい場合はその前に「\」を付けます。
これらを踏まえた上で次に進みましょう。
<HTML> <BODY bgcolor=#ffffff text=#000000> <FORM method="GET" action="test1-5.cgi"> <INPUT type="text" name="test1" size="20"><br> <INPUT type="radio" name="test2" value="はい">はい <INPUT type="radio" name="test2" value="いいえ">いいえ<br> <INPUT type="submit" value="送信"> </FORM> </BODY></HTML> |
#!C:/Perl/bin/perl if($ENV{'REQUEST_METHOD'} eq "POST"){ read(STDIN, $str, $ENV{'CONTENT_LENGTH'}); }else{ $str = $ENV{'QUERY_STRING'}; } @part = split/&/,$str; foreach(@part){ ($key,$val) = split/=/; $val =~ tr/+/ /; $val =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C", hex($1))/eg; $in{$key} = $val; } print <<"EOF"; Content-type: text/html <HTML> <BODY bgcolor="#ffffff" text="#000000"> テキスト入力:$in{'test1'}<br> 選択:$in{'test2'} </BODY></HTML> EOF exit; |
突然複雑になったかもしれません。
まず、"test1-5.html"のFORMタグに関しては次でまとめて解説するので、要点だけ述べます。
methodは後回し。actionは情報を送信するCGIのURLで、その後の各入力フォームには、送信ボタン以外は必ずname属性がついている事に注意してください。入力フォームにはいくつかの形式がありますが、ここで付けられた名前と入力内容がセットになる事は共通です。
さて、次は[こちら]のCGIを見てください。同じ入力フォームが2つ並んでいます。一方はFORMタグのmethod属性がGET(初期値)、もう一方はPOSTとなっています。それぞれ適当な文字を書いて送信ボタンを押してみましょう。
どちらも大抵の場合入力した文字列がそのまま結果に表示されたはずですが、実行後のブラウザ上部のアドレスバーを見ると、上の場合はcgiのアドレスの後に「?test1=xxxx&test2=xxxx」という様な文字列がくっついていると思います。これがGETメソッドとPOSTメソッドの違いです。
更に上の方を試すと、半角文字はそのままの形でアドレスバーに表示されますが、全角文字は%XXの集まった形になっている他、半角スペースは「+」になっているはずです。
これらはURLエンコーディングと言って、メソッドがGETであろうとPOSTであろうとフォームからの入力情報は必ず「名前1=値1&名前2=値2…」の形の1行のデータにし、7ビットASCIIでない特殊文字はASCII文字コードに変換されるのです。
それを何となくで良いので理解したら、"test1-5.cgi"を見てみましょう。
まず、先ほどのGETとPOSTでは送られた情報の受け取り方が違います。
どちらも環境変数を使って受け取るのですが、GETの場合は「$ENV{'QUERY_STRING'}」にそのままURLエンコーディングされた文字列が、POSTの場合は標準入力(STDIN)に同様の文字列が格納され、「$ENV{'CONTENT_LENGTH'}」に送信されたバイト数が送られてきます。そして「$ENV{'REQUEST_METHOD'}」には送信形式(GETかPOST)が格納されます。
そこでまず$ENV{'REQUEST_METHOD'}から送信形式がGETかPOSTかを判断し、POSTならSTDINから$ENV{'CONTENT_LENGTH'}バイトだけ$strに、GETなら$ENV{'QUERY_STRING'}をそのまま$strに代入します。
こうすると送信形式がどちらであってもその後の操作は同じで済みますが、送信形式がまずどちらかであると決まっているならそちらだけ処理しておく方が良いでしょう。
また、$ENV{'QUERY_STRING'}に格納できるデータ量には限界があるので、GETの場合はあまり大きなデータ送信(例えば掲示板のコメントやファイル送信)には向いていません。
続いてURLエンコーディングされた文字列のデコードです。
まず、複数の入力フォームがある場合、「名前=値」の形が「&」で区切られているのでsplit関数を使ってそれぞれの組を配列@partに格納します。split関数の詳しい使い方の解説はとりあえず省きます。とにかくこの書き方で「@part = ('名前1=値1','名前2=値2',…)」という形になります。また、入力内容に「&」があった場合、URLエンコーディングの際に文字コード変換されているので問題ありません。
次にforeachでループして@partの内容を1つ目から順に処理します。それぞれの内容は「名前=値」となっているので、=で切り分けてそれぞれ$keyと$valに代入します。
それからURLエンコーディングの際に半角スペースは「+」になっているので元に戻し、ASCII文字コードに変換された文字列も元に戻します。そして連想配列%inに$key(名前)をキーにして$val(値)を取り出します。
この部分はおそらくPerlCGIを学ぶ上で最もつまづきやすいところだと思いますが、ひとまずこれをひとまとまりにして、こうすれば入力フォームの内容を「$in{'名前'} = "値"」で扱える、という風にしてしまいましょう(何
ここをクリアしない事にはフォームから送られた情報を処理できないので先に進めないのですが、この形は誰が書いても変数等の名前が変わるくらいで大差が無く、特殊な動作を行わない限りはほぼこの通りで使用できます。ですからいきなりここを理解しようとするより、他の処理を通してこの部分の内容を理解する方が近道であると思います。
ここまで来れば後はフォームの入力内容を$in{'test1'}と$in{'test2'}で表示するだけですが、先ほどまでと書き方が違います。
「<<"XXX";〜XXX」はヒアドキュメントと言って、長い文字列を扱う場合に非常に便利です。
""内の文字列(終端文字列)が出るまでの間、「<<"XXX";」の次の行以降の文字列をひとまとまりとして扱ってくれるのですが、この中では改行がそのまま有効になる上、「"」を「\"」等と書く必要がありません。HTMLなどはほぼそのまま書いて構わないのです。しかも変数はちゃんと解釈されるので良い事尽くめです。
ちなみに終端文字列はなんでも良いのですが、その文字列が単独行になっていないと有効になりません。
さて、ここまでで「フォームからの入力を受け取り、処理を行ってファイルを読み書きして表示する」というCGIの基本的な処理をざっと見て来ました。これが基本でありある意味全てであると言えます。ほとんどのCGIはこれらの処理を複雑にしていっただけです。
それでは試しに掲示板を作ってみましょう(何 掲示板はモノにもよりますがCGIの中では最もポピュラーで基本的な処理をしていると言えます。次からは掲示板を作る過程を通してよく使われる関数を紹介して行きます。