おもこん

おもこんは「思いつくままにコンピュターの話し」の省略形です

cgiの作り方(5)

cgiでウェブページを送る方法

cgiでは今回perlを使っています。perlの出力、例えばprint文で出力した内容は、サーバによってクライアントに送信されます。ですから、html文書をprint文で出力すれば良いということです。ただし、いきなりhtml文書を書くのではなく、最初にhttpヘッダ、次に空行を出力してからhtml文書を出力しなければなりません。

cgiで出力するhttpヘッダ

cgiで出力するhttpヘッダは、Content-type が主で、それ以外にもLocationとStatusがありますが、ほとんど使わないでしょう。
print "Content-type: text/html; charset=utf-8\n";
このようにperlのprint文で書きます。以下の内容はtext/htmlすなわちhtml文書だよ、ということを表しています。この書き方はMIMEという書き方で、他にはtext/plainはテキスト文書、image/jpegはjpeg画像などがあります。しかし、text/htmlとすることがほとんどだと思います。後半のcharsetは文字コードを指定するものです。日本語にはいろいろなコードがありますので、自分の使った文字コードを指定します。もしもシフトJISを使ったなら、charset=Shift_JISと書きます。このhttpヘッダの出力の後に必ず空行を出力してください。これを忘れるとcgiは動きません。

サンプルプログラム

サンプルとして、式を入力してもらい、その式をperlのeval文で評価し、答えを返すというスクリプトを示します。
#!/usr/bin/perl

#===========================================================
# 簡易電卓 0.1
#  testCALC.cgi
#===========================================================

if ($ENV{'CONTENT_LENGTH'} > 0) {
  read(STDIN, $query_string, $ENV{'CONTENT_LENGTH'});

  @a = split(/&/, $query_string);
  foreach $x (@a) {
    ($name, $value) = split(/=/, $x);
    $value =~ tr/+/ /;  #スペースの復元
    $value =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C", hex($1))/eg; #特殊文字,日本語の復元
    $FORM{$name} = $value;
  }
  $answer = eval($FORM{'EXPRESSION'});
}

print "Content-type: text/html; charset=utf-8\n";
print "\n";
print "<HTML>\n";
print "<HEAD>\n";
print "<TITLE>簡易電卓</TITLE>\n";
print "</HEAD>\n";
print "<BODY>\n";

if ($ENV{'CONTENT_LENGTH'} > 0) {
  print encodehtml("$FORM{'EXPRESSION'} = $answer")."<BR>\n";
  print "<BR>\n";
}

print "<FORM action=\"testCALC.cgi\" method=\"POST\" accept-charset=\"UTF-8\">\n";
print "  <INPUT type=\"text\" name=\"EXPRESSION\"><BR>\n";
print "<BR>\n";
print "  <INPUT type=submit value=\"送信\">\n";
print "</FORM>\n";
print "\n";

print "</BODY>\n";
print "</HTML>\n";
exit();

#
#  htmlの特殊文字(&"<>)を無効化
#

sub encodehtml() {
  my $s = shift;
  $s =~ s/&/&/g;
  $s =~ s/"/"/g;
  $s =~ s/</</g;
  $s =~ s/>/>/g;
  return $s;
}

サンプルプログラムの解説

まず、POSTデータを取り込む部分です。
if ($ENV{'CONTENT_LENGTH'} > 0) {
  read(STDIN, $query_string, $ENV{'CONTENT_LENGTH'});

  @a = split(/&/, $query_string);
  foreach $x (@a) {
    ($name, $value) = split(/=/, $x);
    $value =~ tr/+/ /;  #スペースの復元
    $value =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C", hex($1))/eg; #特殊文字,日本語の復元
    $FORM{$name} = $value;
  }
  $answer = eval($FORM{'EXPRESSION'});
}
Perlでは、環境変数は$ENV{環境変数名}で参照することが出来ます。これは連想配列です。 CONTENT_LENGTH は、POSTデータの長さを示しています。これが0より大きければ、POSTデータがあるということになります。
read関数で、標準入力から CONTENT_LENGTH の大きさのデータを変数$query_stringに取り込みます。変数名は他の名前でも構いません。
splitを使って、& を区切りとしてデータを分け、配列aに代入します。
for文で配列 a の要素ごとに = を区切りとして、$name と$value に分けて代入します。$nameに入るのは、FORMタグのinput要素の名前ですから、これはアルファベットなどで、URLエンコーディングは関係ありません。それに対して $value はURLエンコーディングでデータが変わっている可能性があるので、デコードします。デコードしたデータは連想配列 $FORM に格納します。
evalを用いて式を計算して、$answer に代入します。

次は出力部分です。
print "Content-type: text/html; charset=utf-8\n";
print "\n";
print "<HTML>\n";
print "<HEAD>\n";
print "<TITLE>簡易電卓</TITLE>\n";
print "</HEAD>\n";
print "<BODY>\n";

if ($ENV{'CONTENT_LENGTH'} > 0) {
  print encodehtml("$FORM{'EXPRESSION'} = $answer")."<BR>\n";
  print "<BR>\n";
}

print "<FORM action=\"testCALC.cgi\" method=\"POST\" accept-charset=\"UTF-8\">\n";
print "  <INPUT type=\"text\" name=\"EXPRESSION\"><BR>\n";
print "<BR>\n";
print "  <INPUT type=submit value=\"送信\">\n";
print "</FORM>\n";
print "\n";

print "</BODY>\n";
print "</HTML>\n";
exit();
最初の行は HTTPヘッダです。空行を出してから、HTML文書の出力になります。ヘッダ部分を出力し、ボディに入ります。
そこで、CONTENT_LENGTH が正、すなわちPOSTデータがあった場合は、式(すなわち$FORM{'EXPRESSION'})イコール 答(すなわち$answer)を出力します。このとき、HTMLでは特殊な役割を持っている文字(&, ", <, >)をHTMLで表示できる形に変換(encodehtml関数)します。

最後は入力用の FORM タグと、INPUT タグを出力して終わります。Perlでは、文字列を表すのに「'」(シングルクォート)と「"」(ダブルクォート)のどちらも使えますが、ダブルクォートの文字列中にダブルクォートは使えません。その場合は「¥"」とエスケープ文字の「¥」を付ければOKです。