CGI

■ 簡単なCGIの例

◆ CGIの基本

ブラウザでそのページにアクセスすると「Hello world!!」と表示する、とても簡単な CGI(CGIスクリプト)の例を紹介します。最初に Content-type 行を、次に空行を書き出すことが肝心です。

cgi1.cgi
#!/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の設置方法

一般的なプロバイダにおける CGI の設置方法は以下のようになりますが、各プロバイダによって作法が異なる点もあります。設置の際には必ずプロバイダの説明を参照してください。

  1. CGIスクリプトを作成する。拡張子を .cgi とする。先頭行は #!/usr/local/bin/perl や #!/usr/bin/perl など、サーバーやプロバイダの環境に合わせて変更する。
  2. CGIスクリプトを FTPクライアントソフト で転送する。この際、転送モードを必ず テキストモードアスキーモード)で転送する。
  3. 必要に応じて、.htaccess などのファイルを編集する。(プロバイダやサーバーの説明書を参照)
  4. FTPクライアントソフトで、サーバーに転送した CGIスクリプトの属性(パーミッション)を 0755(rwxr-xr-x) や 0700(rwx------)に変更する。
  5. ブラウザから、http://~/~/cgi1.cgi のようにして呼び出す。
◆ ブラウザ表示結果
Hello world!!

■ ブラウザに関する情報を得る

◆ 説明

Perl スクリプトが CGI として呼び出されるとき、いろいろな環境変数を参照することができます。

cgi2.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で参照可能な環境変数一覧」を参照してください。


ひとくちメモ

上記の「Mozilla/4.0 (compatible; MSIE 6.0...)」は IE6.0 のブラウザ情報ですが、Mozilla は Netscape 社のブラウザを意味します。数年前までは Netscape 社のブラウザがシェアの大半を占めていて、「ブラウザ情報が Mozilla で始まっていたら JavaScript を埋め込んだページを返す」などのページがあったため、IE などのブラウザも、「Netscape 4.0 との互換機能を持った IE6.0 だよ。」という意味で Mozilla で始まるブラウザ情報を送信しているようです。

■ すべての環境変数を表示する

◆ すべての環境変数を表示する

CGI が受け取った環境変数をすべて表示します。

cgi3.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 ページが、フォームに入力した値を読み取ることで動作しています。ここでは、フォームに入力して送信したデータを CGI で受け取る基本的な方法について紹介します。

◆ HTMLの準備

まず、下記のようなHTML文書を作成し、サーバーに設置してください。

cgi4.htm
<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の準備

サーバー側には次の CGI スクリプトを設置してください。

cgi4.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 のページに表示することは危険です。詳細は「クロスサイトスクリプティングに対応する」を参照してください。

◆ GETメソッドの動作

<form> タグの method 属性に GET を指定した場合、「NAMAE=値&TOSHI=値」というデータが CGIスクリプトの URL に付加されて送信されます。CGI では、これを環境変数 QUERY_STRING から読み取ります。URL や環境変数を用いて渡されるので、あまり長いデータに対して用いることはできません。

http://www.xxx.zzz/test/cgi4.cgi?NAMAE=TANAKA&TOSHI=26
◆ POSTメソッドの動作

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 として書き出すので、& や < や > は、&amp;、&lt;、&gt; に変換してやる必要があります。

cgi5.cgi
#!/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/&/&amp;/g;
    $line =~ s/</&lt;/g;
    $line =~ s/>/&gt;/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のエラーページを表示する

◆ CGIのエラーページを表示する

CGI では標準エラー出力への書き出しをうまく扱えないため、die() を使用することができません。ブラウザにエラーメッセージを返すには、下記のようなサブルーチンを作成しておくと便利です。

cgi6.cgi
# エラーページを表示して終了する
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("ファイルのオープンに失敗しました。");

実行例は次のようになります。

ERROR


ファイルのオープンに失敗しました。

■ CGIから画像を表示する

◆ CGIから画像を表示する

CGI で書き出すのはテキストだけとは限りません。下記のようにして、画像ファイルを書き出すこともできます。Content-type を text/html ではなく、image/gif にしています。また、改行コードにマッチしてしまう画像情報が変換されてしまうのを防ぐため、バイナリモードにしています。

cgi7.cgi
#!/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">
◆ MIMEタイプ

text/html や image/gif のようなフォーマット指定を MIMEタイプ と呼びます。MIMEタイプには次のようなものがあります。

■ CGIの結果がキャッシュされないようにする

◆ CGIの結果がキャッシュされないようにする

たまに、CGI の結果がブラウザにキャッシュされてしまい、古いキャッシュデータが表示されてしまうことがあります。CGI の結果がブラウザにキャッシュされるのを防ぐには、no-cache や Expires などの HTTP ヘッダを書き出してやります。no-cache はキャッシュしないことを、Expires はその時刻にキャッシュをクリアすることをブラウザに依頼します。Expire に過去の時刻を指定することで、キャッシュを無効にすることができます。ただし、ブラウザの種類やバージョンにより対応状況は様々なようです。

cgi8.cgi
#!/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の結果が文字化けしないようにする

◆ CGIの結果が文字化けしないようにする

CGIの結果が文字化けしてしまうのを防ぐには、<meta> タグでキャラクタセットを明示的に指定してやります。シフトJIS の時には Shift_JIS を、EUC の時には euc-jp を、JIS の時には iso-2022-jp を指定してやります。

cgi9.cgi
#!/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を用いてCGIを作成する

◆ cgi-lib.pl とは

cgi-lib.pl は、Perl で CGI を作成する際によく用いられるライブラリです。以下のサイトからダウンロードできます。

cgilib.cgi
◆ cgi-lib.pl の使用例

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を用いてCGIを作成する

◆ Cgi.pm とは

Cgi.pm は、Perl で CGI を作成する際に便利な機能を集めたモジュールです。基本的には次のように使用します。

cgi10.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)を作成しないで書き出す方法もあります。

cgi11.cgi
#!/usr/local/bin/perl
use CGI qw/:standard/;
print
    header(),
    start_html("Hello world"),
    h1("Hello world"),
    p("body..."),
    end_html();

これを実行するとブラウザには次のように表示されます。

Hello world

body...
◆ CGI.pmで HTML(XHTML)を書き出す

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 でフォーム変数を参照する

CGI.pm を用いると、フォームに入力した値を簡単に参照することができるようになります。

cgi12.cgi
#!/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スクリプトとは

NPHスクリプト の NPH は No Parsed Header の略です。常の CGIスクリプトは CGI → Webサーバー → ブラウザの経由でデータを渡し、Webサーバーが適切にヘッダを処理しますが、NPHスクリプトでは、CGI → ブラウザに直接データを返すようになります。処理が軽くなる、サーバー負荷が減る、Webサーバーにキャッシュされなくなるなどの利点があります。

◆ NPHスクリプトの作成方法

通常の CGIスクリプト を NPHスクリプトとして実行するには、下記の作業を行ってください。

  1. CGIスクリプトを nph-cgi13.cgi など、nph- ではじまる名前にする。
  2. プログラムの先頭で「HTTP/1.0 200 OK」を書き出す。
  3. ヘッダ部分の改行コードを \n ではなく、\r\n で書き出す。
nph-cgi13.cgi
#!/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 が書き出されます。

nph-cgi14.cgi
#!/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の結果を逐次書き出す

◆ CGIの結果を逐次書き出す

CGI で時間のかかる処理を実行中、1秒毎に o を書き出すなど、CGI が書き出す文字を逐次ブラウザに表示するには、CGIスクリプト、Webサーバー、Webプロキシサーバー、ブラウザすべてが、データをバッファリングしないようにする必要があります。

  1. CGIスクリプトがバッファリングしないように、$| = 1; を記述しておく。
  2. Webサーバーがバッファリングしないように、NPHスクリプト として実装する。

プロキシサーバーのバッファリングは運次第でしょうか。ブラウザは、Internet Explorer がバッファリング無しで表示してくれるようです。

nph-cgi15.cgi
#!/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 ブラウザでは、ブラウザがバッファリングしてしまうため、すべてのデータの送信完了後に、まとめて表示されてしまうようです。


Copyright (C) 2002 杜甫々