ソースコードで学ぶWebプログラミング

Perlで始めるCGIプログラム入門

このページでは、Perl言語で作成したプログラムをWebプログラムとして動作させる方法を説明します。CGIとは、Webサーバと連携してプログラムを動作させる仕組みです。ブラウザからWebサイトにアクセスがあった場合、Webサーバはサーバ内にあるコンテンツを表示します。CGIプログラムでは、このコンテンツを動的に変化させて表示させることが出来ます。

PerlプログラムをCGIで動かす方法

Perlは、スクリプト言語なので、プログラムソースは普通のテキストファイルになっています。これをプログラムとして動かすには、ソースコードを解析し、機械語に翻訳して実行するプログラムが必要になります。このプログラムをインタープリターと呼び、Perl以外にもPythonやRubyがあります。

CGIでは、プログラムソースの一行目にインタープリターのパス(存在する場所)を記述するだけで、動作させることが出来ます。レンタルサーバなどでは、パスが公開されていますが、分からない場合は #!/usr/bin/env perl とすることで、サーバ内のパス情報を元に起動させることが出来ます。

#!/usr/bin/perl # 1行目にインタープリターのパスを記述 #!/usr/bin/env perl # パスが分からない場合 1行目に

プログラムのコードは、テキストエディタで作成できます。CGIプログラムのファイル名は、name.cgiなど.cgiが付いた状態で保存します。ファイル名をindex.cgiにすると、通常http://example.com/のようなファイル名が不要なURLで表示することが出来ます。後はFTPソフトで、レンタルサーバなどへ設置します。またアップロードしたプログラムには、FTPソフトなどで実行権限(パーミッション)を与える必要があります。

PerlのCGIで文字を表示してみる

CGIプログラムでブラウザに文字を表示させてみます。CGIプログラムは、Webサーバにコンテンツを渡すことが出来ますが、テキストやHTML、画像などコンテンツには様々な種類があります。そのため、渡すコンテンツが何なのかを指定する必要があり、ヘッダー情報として渡します。

#!/usr/bin/perl # ヘッダー情報、テキストファイルを指定 print "Content-Type: text/plain\n\n"; print 'Hello, World!';

printが表示させるための関数で、text/plainがコンテンツの内容になり、\nが改行になっています。表示させるコンテンツデータは、Hello, World!になります。コンテンツデータ部の前は改行で空ける事になっているため、\nが2つ必要です。また\nが有効となるためには、""で括る必要があります。''で括った場合は改行の意味にはならず、\nの文字列になります。

続いて日本語を表示させてみます。日本語の文字は、同じ文字でもエンコーディングの種類により異なった文字コードになるため、エンコーディングを指定する必要があります。多くのサイト表示や開発環境は utf-8 になるため、プログラムソースはutf-8で保存して下さい。

#!/usr/bin/perl # ヘッダー情報、テキストファイルを指定 print "Content-Type: text/plain; charset=utf-8\n\n"; print 'こんにちは、せかい!';

PerlのCGIで計算をしてみる

今回は、例題を解くかたちで、Perl言語で四則演算をする方法を覚えることにします。Perlの加減乗除は、順番に+ - * /になります。剰余(割った余り)は%で求めることが出来ます。

■例題
5人が集まって、1人が買い物に出かけました。購入金は5人で負担し、割り切れない金額は購入担当者が負担するものとします。所持金10,000円を持って、500円の物を7個、700円の物を4個、購入しました。1人当たりの購入金額と購入担当者の負担額を求めなさい。

#!/usr/bin/perl # 厳格モード use strict; use warnings; print "Content-Type: text/plain; charset=utf-8\n\n"; # 消費税 my $tax = 1.08; # 所持金 my $wallet = 10000; # 人数 my $num = 5; # 商品価格A my $priceA = 500; # 商品価格B my $priceB = 700; # 購入金額 my $total = ($priceA * 7 + $priceB * 4) * $tax; print '購入金額:' . $total . "円\n"; print '残高:' . ($wallet - $total) . "円\n"; print '1人当たり:' . int($total / $num) . "円\n"; print '購入者負担:' . int($total % $num) . "円";

今回は、変数を利用しています。Perlでは、変数の使用時に注意が必要となるため、チェックを行ってくれるモジュールが存在します。use strict;use warnings;の部分が該当します。

Perlでの変数は基本的に、myを付けて宣言します。また数値と文字列を特に変換することなく、「.」(ドット)で結合させることが出来ます。整数値を取るには、int を利用します。

人数を変更したり、購入商品を増やしたり、ポイント還元率を作り、取得ポイントを計算してみて下さい。カスタマイズなどで実際に手を動かさないと身につきません。

PerlのCGIで日時を表示してみる

Perlで日時を取得するには、localtimeを利用します。またtimeでUnixタイムスタンプを取得できます。Unixタイムスタンプとは、1970年1月1日0時0分0秒からの秒数になります。1秒毎の連番として利用できるほか、指定時刻を求める際にも利用できます。

現在日時だけを表示してもなので、今回も例題を解くかたちで覚えます。例題の1万時間とは、プロレベルのスキルを習得するのに必要となる時間とされています。「1万時間の法則」と呼ばれ、イギリスのマルコム・グラドウェル氏が提唱しています。

■例題
1日6時間を訓練に当てたとして、累計1万時間に達するのはいつになるのかを求めよ。

#!/usr/bin/perl use strict; use warnings; print "Content-Type: text/plain; charset=utf-8\n\n"; # 現在時刻を取得 my($sec,$min,$hour,$day,$mon,$year,$wday) = localtime; # 現在日時を指定形式で表示 print sprintf("%d年%02d月%02d日 %02d時%02d分%02d秒",$year+1900,$mon+1,$day,$hour,$min,$sec) . "\n"; # 1日6時間の訓練を1万時間続けた場合 my $time = time + 10000*3600 * (24/6); # 指定時間後の時刻 my($sec,$min,$hour,$day,$mon,$year,$wday) = localtime $time; # 指定時間を指定形式で表示 print sprintf("%02d年%02d月%02d日 %02d時%02d分%02d秒",$year+1900,$mon+1,$day,$hour,$min,$sec);

localtimeでは、時刻のリストが返ってくるので、それぞれが変数に入るようにします。注意点として、年($year)は、西暦に対して1900が引かれた状態になっています。また月($mon)は0-11の範囲になっているため、1を足す必要があります。$wdayには曜日が0-6の範囲で入っています。0が日曜日です。

数値は、sprintfで桁数を揃えたりなどフォーマット化して表示させることが出来ます。%02dで、数値を整数として2桁表示で、不足分を0で埋めて表示できます。printfで桁数揃えと同時にprintを実行できますが、変数として返すsprintfの方をよく使うことになるため、こちらを使います。

timeは秒単位なので、1万時間に3600を掛けて秒単位にします。また1日6時間の場合、1日は24時間なので全体としては4倍の時間が必要になります。1日2時間の場合、1日10時間の場合などを求めてみて下さい。

PerlのCGIで条件分岐を覚える

プログラムは、ある変数の状態で真か偽かを判定して、条件に合わせた処理を行わせる流れになります。Perlでは、if{}else{} でそれぞれの条件に合わせた処理をコーディングします。

また今回からCGIプログラムの処理として、ブラウザからの入力を受け取れるようにします。デコード用のモジュール CgiDecode.pmを使うので、こちらからダウンロードして下さい。下記プログラムのURLへアクセスすると「入力がありません」が表示されると思います。続いてURLの末尾に、?word=文字列を入力後、アクセスしてみて下さい。

#!/usr/bin/perl use strict; use warnings; # デコード用モジュール require 'CgiDecode.pm'; # 変数をセット CgiDecode->new; # our()で使えるように our(%_GET,%_POST,%_COOKIE,@_FILES); print "Content-Type: text/plain; charset=utf-8\n\n"; # word=の入力があった場合 if (exists $_GET{'word'}) { print $_GET{'word'}; } else { print '入力がありません'; }

今度は文字列が表示されると思います。existsが変数(ハッシュ)の中にキーがあるかを判定し、あった場合にif以下のブロック内の処理を実行しています。この時、else以下のブロックは処理されないため、表示されません。

PerlのCGIで繰り返し処理を覚える

プログラムで同じ処理を何度も行なう場合は、for(){} が利用されます。Perlでは、初期値と条件、インクリメントを使う方法と、範囲を指定する方法があります。インクリメントとは、変数++で1増やすことができ、ディクリメント変数--で1減らすこともできます。範囲の場合は、繰り返し回数が見た目にも分かりやすいため、今回はこちらを利用します。

# 初期値と条件、インクリメント for (my $i=0;$i<10;$i++) {} # 初期値と条件、ディクリメント for (my $i=10;0<$i;$i--) {} # 範囲 for (1..10) {} # 範囲で入力する変数を指定する場合 for my $i (1..10) {}

初期値を代入する方法では、変数$i が増減している数値になります。範囲の方法では、変数 $_ に数値が入っています。繰り返し処理では、他に foreach(){}、while(){} がありますが、いずれも初期状態では $_ に値が入ります。

for文の習得でよく使われるピラミッド表示をPerlでも行ってみます。ピラミッド表示とは、指定文字でのアスキーアートでピラミッド構造を構築する処理です。今回は*(アスタリスク)と「 」(スペース)で構築します。

■例題
*とスペースで、入力数に応じた段数のピラミッドを表示せよ。

初めから正解のかたちでコーディングは普通できないので、試行錯誤しながら作成していきます。まず普通に段数ごとに*を増やす処理を実装します。

#!/usr/bin/perl use strict; use warnings; require 'CgiDecode.pm'; CgiDecode->new; our(%_GET,%_POST,%_COOKIE,@_FILES); print "Content-Type: text/plain; charset=utf-8\n\n"; # 入力があり、数値の場合 if (exists $_GET{'n'} && $_GET{'n'} =~ /[^\d]/) { # *を入れておく変数 my $t; # 段数 for (1..$_GET{'n'}) { # 段の初めに初期化 $t = ''; # その段の*を作成 for (1..$_) { $t .= '*'; } print $t . "\n"; } } else { print '入力がありません'; }

1つ目のfor文が段数を決定し、2つ目のfor文がその段での*の数を決定することになります。$_に、for文で増加させている数値が入っています。段の*の数は、ここでは段数である$_にしてみます。ここまでで、表示はピラミッドの半分の状態になっていると思います。

# ?n=5 とした場合 * ** *** **** *****

この表示に、スペースを追加すればピラミッド構造を構築できますが、*の文字数から考えて、偶数の段と奇数の段ではうまく真ん中が揃いません。そのため奇数の段のみのピラミッドにするか、偶数の段をスペースで奇数にするかで解決する必要があります。今回は、奇数の段のみのピラミッドにしてみます。

# ?n=5 とした場合 スペースで調節 * ** *** **** *****

次にスペースを入れる方法を考えます。一番下の段を0とした場合、1段目に1個、2段目に2個とスペースを増やせば良さそうです。ただし、現在の処理では一番上の段から処理しているため、上からのアルゴリズムにする必要があります。一番上の段のスペースの数は、4個で、全体の段数5からその段数1を引いた数になっています。

#!/usr/bin/perl use strict; use warnings; require 'CgiDecode.pm'; CgiDecode->new; our(%_GET,%_POST,%_COOKIE,@_FILES); print "Content-Type: text/plain; charset=utf-8\n\n"; if (exists $_GET{'n'} && $_GET{'n'} =~ /[^\d]/) { # *を入れておく変数 my $t; # *の数を決める変数 my $d; # 段数 for (1..$_GET{'n'}) { # その段に含まれる*の数を奇数個に $d = $_*2 - 1; # 段の初めに初期化 $t = ''; # 全体の段数からその段の段数を引いた数のスペースを追加 for (1..($_GET{'n'}-$_)) { $t .= ' '; } # その段の*を作成 for (1..$d) { $t .= '*'; } print $t . "\n"; } } else { print '入力がありません'; }

実行結果は下記になります。

# ?n=5 とした場合 * *** ***** ******* *********

偶数の段にスペースを入れて奇数にする方法も掲載しておきます。

#!/usr/bin/perl use strict; use warnings; require 'CgiDecode.pm'; CgiDecode->new; our(%_GET,%_POST,%_COOKIE,@_FILES); print "Content-Type: text/plain; charset=utf-8\n\n"; if (exists $_GET{'n'} && $_GET{'n'} =~ /[^\d]/) { # *を入れておく変数 my $t; # 段数 for (1..$_GET{'n'}) { # 段の初めに初期化 $t = ''; # 全体の段数からその段の段数を引いた数のスペースを追加 for (1..($_GET{'n'}-$_)) { $t .= ' '; } # その段の*を作成 for (1..$_) { # ここでスペースも追加 $t .= '* '; } # 末尾のスペースを除去 $t =~ s/s$//; print $t."\n"; } } else { print '入力がありません'; }

実行結果は下記になります。

# ?n=5 とした場合 * * * * * * * * * * * * * * *

PerlのCGIでセキュリティ問題を確認する

CGIプログラムでは、ユーザーからの入力を受け取って処理を行なうため、入力データの内容により想定外の結果を引き起こすことがあります。主にブラウザにHTMLタグを表示する際に起こる問題(クロスサイトスクリプティング)と、DBなどに保存する際に起こる問題(SQLインジェクション)があります。

今回は、不正なタグを受け取った場合の問題について確認します。今まではテキストデータとして表示を行っていたため、問題は起こりませんでしたが、HTMLとして表示を行なう場合は、エスケープを行わないと、サイトの表示が崩れたり、javascriptを実行されたりします。

下記では、エスケープを行わず、入力を直接表示しています。?word=<h1>などでアクセスすると、タグの機能によりWebサイトのデザインが壊れることになります。

#!/usr/bin/perl use strict; use warnings; require 'CgiDecode.pm'; CgiDecode->new; our(%_GET,%_POST,%_COOKIE,@_FILES); # htmlを表示 print "Content-Type: text/html; charset=utf-8\n\n"; # word=の入力があった場合 if (exists $_GET{'word'}) { print $_GET{'word'}; } else { print '入力がありません'; } print '<p>サイトのコンテンツ</p>';

Content-Typeの部分を、text/htmlにしています。以降の表示はHTMLとして扱われるため、タグで使われる文字はエスケープする必要があります。上記プログラムのご確認後は、サーバから削除して下さい。