ブラウザでそのページにアクセスすると「Hello world!!」と表示する、とても簡単な CGI(CGIスクリプト)の例を紹介します。最初に Content-type 行を、次に空行を書き出すことが肝心です。
#!/usr/local/bin/perl print <<END_OF_DATA; Content-type: text/html <html> <head><title>CGI TEST</title></head> <body> Hello world!! </body> </html> END_OF_DATA
一般的なプロバイダにおける CGI の設置方法は以下のようになりますが、各プロバイダによって作法が異なる点もあります。設置の際には必ずプロバイダの説明を参照してください。
Hello world!!
Perl スクリプトが CGI として呼び出されるとき、いろいろな環境変数を参照することができます。
#!/usr/local/bin/perl print <<END_OF_DATA; Content-type: text/html <html> <head><title>CGI TEST</title></head> <body> $ENV{'HTTP_USER_AGENT'} をご利用ですね。 </body> </html> END_OF_DATA
これをブラウザで表示すると次のようになります。
Mozilla/4.0 (compatible; MSIE 6.0; Windows 98) をご利用ですね。
これにより、例えば、アクセスしてきたブラウザに応じて表示するページを変更するといったことが可能になります。ブラウザ名($ENV{'HTTP_USER_AGENT'})の他にも、アクセス元ホスト名($ENV{'REMOTE_HOST'})、リンク元のページ情報($ENV{'HTTP_REFERER'})などを取得することができます。その他に参照可能な環境変数については「CGIで参照可能な環境変数一覧」を参照してください。
CGI が受け取った環境変数をすべて表示します。
#!/usr/local/bin/perl print "Content-type: text/html\n\n"; print "<html>\n"; print "<head><title>CGI TEST</title></head>\n"; print "<body>\n"; foreach $key (sort(keys(%ENV))) { print "$key = $ENV{$key}<br>\n"; } print "</body>\n"; print "</html>\n";
結果は例えば次のようになります。
AUTH_TYPE = Basic CONTENT_LENGTH = 0 DOCUMENT_ROOT = /home/htdocs GATEWAY_INTERFACE = CGI/1.1 HTTP_ACCEPT = image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */* HTTP_ACCEPT_LANGUAGE = ja HTTP_CACHE_CONTROL = max-age=259200 HTTP_CONNECTION = keep-alive HTTP_HOST = www.xxx.zzz HTTP_USER_AGENT = Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) PATH = /usr/local/bin:/usr/bin:/bin QUERY_STRING = REMOTE_ADDR = 192.168.0.1 REMOTE_HOST = 192.168.0.1 REMOTE_USER = REQUEST_METHOD = GET REQUEST_URI = /test/cgi3.cgi SCRIPT_FILENAME = /home/htdocs/test/cgi3.cgi SCRIPT_NAME = /test/cgi3.cgi SERVER_ADMIN = webmaster@www.xxx.zzz SERVER_NAME = www.xxx.zzz SERVER_PORT = 80 SERVER_PROTOCOL = HTTP/1.1 SERVER_SOFTWARE = Apache/1.3.22 TZ = Japan
掲示板、チャット、アンケートフォーム、予約システムなど、多くの CGI ページが、フォームに入力した値を読み取ることで動作しています。ここでは、フォームに入力して送信したデータを CGI で受け取る基本的な方法について紹介します。
まず、下記のようなHTML文書を作成し、サーバーに設置してください。
<html> <head><title>テスト</title></head> <body> <form method="POST" action="cgi4.cgi"> <div>名前:<input type="text" name="NAMAE"></div> <div>年齢:<input type="text" name="TOSHI"></div> <input type="submit" value="送信"> </form> </body> </html>
これをブラウザで表示すると次のような画面になります。
method には POST または GET を、action にはサブミットボタンを押したときに呼び出す CGI スクリプトの URL を指定します。HTML ファイルと CGI スクリプトが別のマシンにある場合は、action="http://www.xxx.zzz/test/cgi4.cgi" のように、完全 URL を記述することもできます。
サーバー側には次の CGI スクリプトを設置してください。
#!/usr/local/bin/perl # フォームパラメータを読みこむ(%FORM に値が代入される) ReadFormData(*FORM); # 結果を書き出す #===================================================================== print <<END_OF_DATA; Content-type: text/html; <html> <head><title>TEST</title></head> <body> <div>名前:$FORM{'NAMAE'}</div> <div>年齢:$FORM{'TOSHI'}</div> </body> </html> END_OF_DATA #===================================================================== # フォームの入力値を読み取る sub ReadFormData { local(*FORM) = @_; local($buf, $tmp, $name, $value); # POSTの場合は標準入力から、GETの場合はQUERY_STRINGから情報を得る if ($ENV{'REQUEST_METHOD'} eq "POST") { read(STDIN, $buf, $ENV{'CONTENT_LENGTH'}); } else { $buf = $ENV{'QUERY_STRING'}; } # & で分割し、それぞれについて foreach $tmp (split(/&/, $buf)) { # イコール(=)名前と値に分割 ($name, $value) = split(/=/, $tmp, 2); # プラス(+)をスペースに置換 $value =~ tr/+/ /; # %nn の形式を特殊文字に置換 $value =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C", hex($1))/eg; # 連想配列 %FORM に代入 $FORM{$name} = $value; } }
フォームの名前欄に TANAKA、年齢に 26 を入力して送信すると、ブラウザに次のように表示されます。
名前:TANAKA 年齢:26
注:実際には、入力された文字をそのまま HTML のページに表示することは危険です。詳細は「クロスサイトスクリプティングに対応する」を参照してください。
<form> タグの method 属性に GET を指定した場合、「NAMAE=値&TOSHI=値」というデータが CGIスクリプトの URL に付加されて送信されます。CGI では、これを環境変数 QUERY_STRING から読み取ります。URL や環境変数を用いて渡されるので、あまり長いデータに対して用いることはできません。
http://www.xxx.zzz/test/cgi4.cgi?NAMAE=TANAKA&TOSHI=26
method 属性に POST を指定した場合、「NAMAE=値&TOSHI=値」というデータが、CGI スクリプトの標準入力(STDIN)に渡されます。また、データのサイズが環境変数 CONTENT_LENGTH で渡されます。長いデータの送信には POST が適しています。
NAMAE=TANAKA&TOSHI=26
送信されるデータは、スペースはプラス記号(+)に置き換えられ、一部を除く記号や日本語は %nn 形式(nn は16進数の文字コード)に置き換えられて送信されます。例えば、名前欄に「TANAKA ICHIRO」、年齢欄に「?」を入力した場合は次のようにエンコード(符号化)されて送信されます。
NAMAE=TANAKA+ICHIRO&TOSHI=%3F
CGI 側では、これを適切にデコード(解読)してやる必要があります。
CGI でサーバー側のファイルを読み込み、これを表示します。HTML として書き出すので、& や < や > は、&、<、> に変換してやる必要があります。
#!/usr/local/bin/perl print "Content-type: text/html\n"; print "\n"; print "<html>\n"; print "<head><title>TEST</title></head>\n"; print "<body>\n"; print "====== cgi5.txt ======\n"; print "<pre>\n"; open(IN, "cgi5.txt"); while ($line = <IN>) { $line =~ s/&/&/g; $line =~ s/</</g; $line =~ s/>/>/g; print $line; } close(IN); print "</pre>\n"; print "</body>\n"; print "</html>\n";
実行例は次のようになります。
====== cgi5.txt ====== <<Peach Boy>> A long, long time ago an old man and an old woman lived. One day, the old man went into the mountains to cut firewood and the old woman went to the river to wash...
CGI では標準エラー出力への書き出しをうまく扱えないため、die() を使用することができません。ブラウザにエラーメッセージを返すには、下記のようなサブルーチンを作成しておくと便利です。
# エラーページを表示して終了する sub ErrorExit { local($msg) = @_; ShowErrorPage($msg); exit(1); } # エラーページを表示する sub ShowErrorPage { local($msg) = @_; print <<END_OF_DATA; Content-type: text/html; <html> <head><title>ERROR</title></head> <body> <h1>ERROR</h1> <hr> $msg <hr> </body> </html> END_OF_DATA }
これを、スクリプトの中から次のようにして利用します。
open(IN, "data.txt") || ErrorExit("ファイルのオープンに失敗しました。");
実行例は次のようになります。
CGI で書き出すのはテキストだけとは限りません。下記のようにして、画像ファイルを書き出すこともできます。Content-type を text/html ではなく、image/gif にしています。また、改行コードにマッチしてしまう画像情報が変換されてしまうのを防ぐため、バイナリモードにしています。
#!/usr/bin/perl # 画像を読みこむ open(IN, "gazou.gif"); binmode(IN); # バイナリモードで読み出す read(IN, $image, -s "gazou.gif"); close(IN); # 画像を書き出す。 binmode(STDOUT); # バイナリモードにする print "Content-type: image/gif\n"; # image/gif で書き出す print "\n"; print $image;
この CGI スクリプトを、HTML ファイルから次のようにして呼び出します。
<img src="cgi7.cgi">
text/html や image/gif のようなフォーマット指定を MIMEタイプ と呼びます。MIMEタイプには次のようなものがあります。
たまに、CGI の結果がブラウザにキャッシュされてしまい、古いキャッシュデータが表示されてしまうことがあります。CGI の結果がブラウザにキャッシュされるのを防ぐには、no-cache や Expires などの HTTP ヘッダを書き出してやります。no-cache はキャッシュしないことを、Expires はその時刻にキャッシュをクリアすることをブラウザに依頼します。Expire に過去の時刻を指定することで、キャッシュを無効にすることができます。ただし、ブラウザの種類やバージョンにより対応状況は様々なようです。
#!/usr/local/bin/perl ($sec, $min, $hour, $mday, $mon, $year) = localtime(); $time = sprintf("%04d/%02d/%02d %02d:%02d:%02d", $year + 1900, $mon + 1, $mday, $hour, $min, $sec); print <<END_OF_DATA Content-type: text/html Pragma: no-cache Cache-Control: no-cache Expires: Thu, 01 Dec 1994 16:00:00 GMT <html> <head> <title>TEST</title> </head> <body> $time </body> </html> END_OF_DATA
CGIの結果が文字化けしてしまうのを防ぐには、<meta> タグでキャラクタセットを明示的に指定してやります。シフトJIS の時には Shift_JIS を、EUC の時には euc-jp を、JIS の時には iso-2022-jp を指定してやります。
#!/usr/local/bin/perl print <<END_OF_DATA Content-type: text/html <html> <head> <meta http-equiv="Content-type" content="text/html; charset=Shift_JIS"> <title>TEST</title> </head> <body> : 本文 : </body> </html> END_OF_DATA
<meta http-equiv="~" content="~"> を記述することは、HTTP のヘッダ部(CGIの書き出しから空行までの間)に記述するのとほぼ同じ意味になるので、上記のスクリプトは次のように記述することもできます。
#!/usr/local/bin/perl print <<END_OF_DATA Content-type: text/html; charset=Shift_JIS <html> <head> :
cgi-lib.pl は、Perl で CGI を作成する際によく用いられるライブラリです。以下のサイトからダウンロードできます。
cgi-lib.pl の使用例を以下に示します。
#!/usr/local/bin/perl # ライブラリを読み込む require "cgi-lib.pl"; # フォームに入力されたデータを読み取る ReadParse(\%FORM); # CGI のヘッダ(Content-type: text/html)を書き出す print PrintHeader(); # HTML のヘッダ部分を書き出す print HtmlTop("ここにタイトルを書きます"); # フォームに入力されたデータを書き出す while (($name, $value) = each(%FORM)) { print "<div>$name = $value</div>\n"; } # HTML のフッタ部分を書き出す print HtmlBot();
他にも次のようなサブルーチンが定義されています。
# エラーページを書き出す CgiError("ファイルの読み込みに失敗しました。"); # 環境変数の一覧を書き出す print PrintEnv();
Cgi.pm は、Perl で CGI を作成する際に便利な機能を集めたモジュールです。基本的には次のように使用します。
#!/usr/local/bin/perl use CGI; $q = new CGI; print $q->header(-charset => "Shift_JIS"), $q->start_html('Hello world'), $q->h1('Hello world'), $q->p('body...'), $q->end_html();
次のように、CGI オブジェクト($q)を作成しないで書き出す方法もあります。
#!/usr/local/bin/perl use CGI qw/:standard/; print header(), start_html("Hello world"), h1("Hello world"), p("body..."), end_html();
これを実行するとブラウザには次のように表示されます。
header() は Content-type ヘッダを書き出します。Content-type やキャラクタセット情報を指定することができます。
print $q->header(); print $q->header("text/html"); print $q->header(-type => "text/html", -charset => "Shift_JIS");
start_html() は、HTML の先頭から <body> までを書き出します。引数には <title>~</title> のタイトルを指定します。最近のバージョンでは、HTML の次世代の規格である、XHTML の文法に従ったタグを書き出すようです。
print $q->start_html("THIS IS A TITLE");
h1() は <h1>~</h1> を書き出します。
print $q->h1("THIS IS A HEADER");
他にもいろいろな関数が用意されていますので、perldoc CGI を実行して参照してください。
CGI.pm を用いると、フォームに入力した値を簡単に参照することができるようになります。
#!/usr/local/bin/perl use CGI; $q = new CGI; print $q->header(-charset => "Shift_JIS"), $q->start_html("CGI.pmテスト"), $q->h1("CGI.pmテスト"), "名前は ", $q->param('NAMAE'), " です。", "年齢は ", $q->param('TOSHI'), " です。", $q->end_html();
NPHスクリプト の NPH は No Parsed Header の略です。常の CGIスクリプトは CGI → Webサーバー → ブラウザの経由でデータを渡し、Webサーバーが適切にヘッダを処理しますが、NPHスクリプトでは、CGI → ブラウザに直接データを返すようになります。処理が軽くなる、サーバー負荷が減る、Webサーバーにキャッシュされなくなるなどの利点があります。
通常の CGIスクリプト を NPHスクリプトとして実行するには、下記の作業を行ってください。
#!/usr/local/bin/perl binmode(STDOUT); print "HTTP/1.0 200 OK\r\n"; print "Content-type: text/html\r\n"; print "\r\n"; print <<END_OF_DATA; <html> <head> <title>NPH Script Sample</title> </head> <body> This is a sample of nph script </body> </html> END_OF_DATA
サーバープッシュ とは、サーバー側から新しいページを送り込むことにより、ブラウザの画面を定期的に書き換える技術です。Netscape のブラウザは対応していますが、Internet Explorer では対応していないようです。データがキャッシュされてしまわないように、前述の NPHスクリプト として実装します。下記の例では、3秒毎に next_page が書き出され、10回目には next_page が undef を返し、last_page が書き出されます。
#!/usr/local/bin/perl use CGI::Push qw(:standard); $msg[1] = "むかし、むかし、あるところに、"; $msg[2] = "おじいさんと おばあさんが すんでいました。"; $msg[3] = "おじいさんは山へしばかりに、"; $msg[4] = "おばあさんは川へせんたくに出かけました。"; do_push( -next_page => \&next_page, # 次のページ -last_page => \&last_page, # 最後のページ -delay => 3); # 3秒毎に表示 sub next_page { my($q, $counter) = @_; if ($counter >= 5) { return undef; } # 5回目になると終了 return "<html>\n" . "<head><title>桃太郎</title></head>\n" . "<body>$msg[$counter]</body>\n" . "</html>\n"; } sub last_page { my($q, $counter) = @_; return "<html>\n" . "<head><title>桃太郎</title></head>\n" . "<body>続きはまた来週</body>\n" . "</html>\n"; }
CGI で時間のかかる処理を実行中、1秒毎に o を書き出すなど、CGI が書き出す文字を逐次ブラウザに表示するには、CGIスクリプト、Webサーバー、Webプロキシサーバー、ブラウザすべてが、データをバッファリングしないようにする必要があります。
プロキシサーバーのバッファリングは運次第でしょうか。ブラウザは、Internet Explorer がバッファリング無しで表示してくれるようです。
#!/usr/local/bin/perl $| = 1; # キャッシュしないようにする binmode(STDOUT); # 改行コード変換を抑制する print "HTTP/1.0 200 OK\r\n"; print "Content-type: text/html\r\n"; print "\r\n"; print "<html>\n"; print "<head><title>TEST</title></head>\n"; print "<body>\n"; print "start\n"; for ($i = 0; $i < 10; $i++) { print "o\n"; sleep(1); } print "end\n"; print "</body>\n"; print "</html>\n";
これを Internet Explorer から呼び出すすると、1秒間に o が1つずつ表示されます。
start o o o o o o o o o o end
Netscape ブラウザでは、ブラウザがバッファリングしてしまうため、すべてのデータの送信完了後に、まとめて表示されてしまうようです。