CGI.pm を使ってみよう

Perl で CGI を開発するうえで役に立つ Perl モジュールはいくつもありますが、 その中でも有名なモジュールと言えば CGI.pm でしょう。

かつては Perl5 に標準モジュールとして組み込まれていた定番モジュールです。 しかし、Perl による CGI 開発が少なくなってきたという動向も影響したせいか、 最新の Perl5 では標準モジュールから外されてしまいました。 しかし、いまなお、多くのレンタルサーバーで利用することができます。

本記事では、CGI.pm の基本的な使い方を解説します。 また、もし最新の Perl5 の環境で CGI.pm がインストールされていないレンタルサーバーでも、 CGI.pm を組み込む方法についても解説します。

目次

CGI.pm のバージョンアップに伴う変化

前述の通り、かつて CGI.pm は Perl5 の標準モジュールでした。 そのため、Perl5 がインストールされた環境であれば、ほぼ CGI.pm を利用することができました。 Perl 5.22 以降、CGI.pm は標準モジュールから外されることになりました。 ご利用のレンタルサーバーによっては、すでに Perl 5.22 以上がインストールされ、 CGI.pm が利用できないかもしれません。

CGI.pm が Perl 標準モジュールから外された大きな理由は、一言でまとめるとするなら、 時代遅れ、ということです。 Perl に限らず、ウェブアプリケーションの開発ではフレームワークの利用が当たり前です。 Perl なら Mojolicious, Dancer2, Catalyst, PSGI/Plack あたりが有名でしょう。 大規模なシステム開発だけでなく、小規模なシステム、さらにはプロトタイピングに至るまで、 もしこれらのフレームワークが利用できる環境があるなら、 そのどれかを使った方が良いことは言うまでもありません。

残念ながら、CGI.pm はフレームワークにはなれず、あくまでもちょっとした便利なライブラリーに過ぎません。 ウェブアプリケーション開発で必要な機能を広範囲にサポートしてくれるわけではありません。 こういった事情から、Perl にとって必要不可欠なモジュールとは言えなくなりました。

さらに、機能面でも変化があります。 分かりやすい点で言うと、HTML 生成機能が丸ごと削除されました。 HTML 生成機能とは、このようなコードで HTML タグを出力します。 これで <HR> が出力されます。

print $q->hr;

近年ではテンプレートエンジンを使うのが当たり前になってきました。 前述のフレームワークにはテンプレートエンジンが含まれていますし、 単独でテンプレートエンジンが欲しいのであれば、 Template::Toolkit が有名です。 画面デザインが Perl コードにハードコーディングされるのは、 保守性の観点からデメリットが大きいと言えます。 そのため、CGI.pm の HTML 生成機能そのものの存在価値が無くなってきました。

以上の話は、 CGI.pm の公式ドキュメント にも詳しく書かれています。 また、CGI.pm を使わないならどうすれば良いかについては、 CGI::Alternatives というドキュメントに記載されています。 英文ですが、興味のある人は読んでみると良いでしょう。

以上、CGI.pm が時代遅れで使わないほうが良いと説明しましたが、 それはあくまでもフレームワークが利用できる環境であればの話です。 フレームワークをインストールすることができない環境でウェブシステムを開発しなければいけない状況であれば、 今なお CGI.pm は役に立つはずです。何も無いよりはましです。 具体的には共有サーバーでも動作するウェブアプリケーションを開発する場合などが当てはまります。 もしそのような状況であれば、CGI.pm も検討してみてはいかがでしょう。

CGI.pm を自前で設置する方法

前述の通り、CGI.pm は Perl 5.22 以降は標準モジュールではありませんので、 ご利用のサーバーにはインストールされていないかもしれません。 その場合は、必要なモジュールをホームディレクトリにコピーすれば CGI.pm を利用できますので、その手順を解説します。

まずは、ご利用の環境で、CGI.pm が利用できるかをチェックしてみましょう。 次のコードを check.cgi として保存します。 改行コードは LF としてください。

#!/usr/bin/env perl
use strict;
use warnings;
use CGI;

my $q = CGI->new;
print $q->header();
print "OK";

この check.cgi をサーバーにアップロードし、 レンタルサーバー指定のファイルパーミッションに変更します。 多くのサーバーでは 704 または 755 で動作するはずです。

もし OK とブラウザーに表示されれは、ご利用の環境で CGI.pm は利用できます。 もしエラーが表示されたら、CGI.pm を自前で用意しましょう。

まず CGI.pm はいくつか他のモジュールに依存しています。 それら依存モジュールも標準モジュールではないため、 依存しているモジュールのすべてが必要です。 以下のリンクから、すべての依存モジュールをダウンロードしてください。 以下はすべて meta::cpan のリンクです。 各モジュールのページの左側メニューに Download リンクがあるはずです。 そこから最新版のモジュールをダウンロードしてください。

まず、extlib というフォルダを作ってください。実は名前は何でも構いません。 次に、ダウンロードしたモジュールを展開します。 すると、いずれにも lib フォルダがあるはずです。 この lib フォルダの中身をすべて extlib の中にコピーします。 すると、次のようなファイル構成になるはずです。

📂 extlib/
    📂 CGI/
    📂 File/
    📄 CGI.pm
    📄 CGI.pod
    📄 Fh.pm
    📄 FindBin.pm
    📄 parent.pm

先ほど作った check.cgi を次のように書き換えます。4, 5 行目を追加しています。

📄 check.cgi
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/extlib";
use CGI;

my $q = CGI->new;
print $q->header();
print "OK";

この check.cgi をサーバーにアップロードします。 そして、同じ位置に extlib もアップロードします。 check.cgi にブラウザーでアクセスして OK と表示されれは成功です。

CGI.pm のロードとオブジェクト生成

CGI.pm を利用するためには、Perl スクリプト上次のように CGI.pm をロードします。

use CGI;

次に、CGI オブジェクトを生成します。

my $q = CGI->new;

変数名は $q である必要はありません。ご自由に決めてください。 ここでは、すべて $q で統一して説明します。

なお、以前は次のような記述が認められていましたが、現在は非推奨になっていますので注意してください。

my $q = new CGI; # この書き方は非推奨です

リクエストを受け取る

フォームデータを変数に格納する

フォームに入力・選択された値を取得する方法を、以下のフォームを例にしたがって説明します。

<form action="https://www.futomi.com/lecture/form/exec/cgipm1.cgi" method="post">
  <p>ニックネーム:<input type="text" name="nickname" value="たろう"></p>
  <p>性別:
    <input type="radio" name="gender" value="male" checked> 男性
    <input type="radio" name="gender" value="female"> 女性
  </p>
  <input type="submit" value="送信">
</form>

ニックネーム:

性別: 男性 女性

フォームの値を取り出すのは非常に簡単です。 「ニックネーム」の欄(テキストフィールド)の入力データは、

my $nickname = $q->param("nickname");

で取り出せます。入力データは、スカラー変数 $nickname に格納されます。 同様に「性別」の選択項目(ラジオボタン)は、

my $gender = $q->param("gender");

で取り出せます。$gender に格納されるデータは、HTML の input 要素の value 属性に指定された値です。たとえば「女性」を選択すれば $gender には、"female" という文字列が格納されます。

セレクトメニュー (select 要素) やテキストエリア (textarea 要素) でも、 まったく同様に値を取得することができます。

複数選択可能なフォームデータを配列に格納する

では、複数選択可能なフォームであるチェックボックスやスクローリングリストの場合は、 どのように取得するのでしょう。以下の例で説明します。

<form action="https://www.futomi.com/lecture/form/exec/cgipm2.cgi" method="post">
  <p>OS:
    <input type="checkbox" name="os" value="Android" checked> Android
    <input type="checkbox" name="os" value="iOS" checked> iOS
    <input type="checkbox" name="os" value="その他"> その他

  </p>
  <p>メーカー:
    <select name="manufacturer" size="3" multiple>
      <option value="Google" selected>Google</option>
      <option value="Apple" selected>Apple</option>
      <option value="その他">その他</option>
    </select>
  <p>
  <input type="submit" value="送信">
</form>

OS: Android iOS その他

メーカー:

複数選択可能なフォームコントロールの場合には、 以下のように multi_param() メソッドを使って配列で値を受ける取ることができます。

my @os_list = $q->multi_param("os");
my @manufacturer_list = $q->multi_param("manufacturer");

このサンプルのフォームでは、OS に「Android」「iOS」が選択されていますので、 配列 @os_list には、input 要素の value 属性にセットされた「Android」「iOS」の 2 つの要素が格納されます。 同様にメーカーには「Google」「Apple」が選択されていますので、 配列 @manufacturer_list には、 input 要素の value 属性にセットされた 「Google」「Apple」の 2 つの要素が格納されます。

なお、複数選択可能なフォームコントロールの場合、 param() メソッドを使うことは避けましょう。 実は下位互換のために param() メソッドでも配列で値を受け取ることは可能です。 しかし、たとえば、スカラー変数がセットされると期待しているところに、 意図せずに配列がセットされるなどのバグを誘発する恐れがありますので注意してください。 必ず、どの name 属性のフォームコントロールが複数選択可能なのかを理解したうえで、 コードを書くことが肝要です。

投稿された name リストを取り出す

前述の通り、投稿されたフォームの値を取り出すには、HTML の input 要素などの name 属性の値を知っている必要があります。 しかし、実際に投稿されたフォームデータの name 属性の値のリストを取り出すこともできます。

<form action="https://www.futomi.com/lecture/form/exec/cgipm3.cgi" method="post">
  <p>ニックネーム:<input type="text" name="nickname" value="たろう"></p>
  <p>性別:
    <input type="radio" name="gender" value="male" checked> 男性
    <input type="radio" name="gender" value="female"> 女性
  </p>
  <input type="submit" value="送信">
</form>

ニックネーム:

性別: 男性 女性

この例では、2 つのフォームコントロールがあります。それぞれの name 属性の値は nicknamegender で取り出せます。 これらの値を CGI 側で知るには、次のコードを使います。

my @names = $q->param;

配列 @names には、nicknamegender という 2 つの要素が格納されます。

レスポンスを返す

HTTP ヘッダーの出力

CGI でページを出力する際には、必ず HTTP ヘッダーを出力しなければいけません。 CGI.pm は簡単なコードで HTTP ヘッダーを出力する機能を提供しています。

次の例は最もシンプルな HTTP ヘッダー出力の例です。

print $q->header;
print '<!DOCTYPE html><html lang="ja"><meta charset="utf-8"><title>-</title><p>Hello';

これは、次のコードと同じです。

print "Content-Type: text/html\n";
print "\n";
print '<!DOCTYPE html><html lang="ja"><meta charset="utf-8"><title>-</title><p>Hello';

ほんの少しですが、コードが短くなります。header メソッドは Content-Type: text/html の後に改行を 2 つ出力してくれますので、その分、コードがスッキリするのではないでしょうか。

前述のサンプルは、Apache の設定にもよりますが、実は日本語を表示しようとすると、ブラウザーで文字化けが発生する可能性が高くなります。 できることなら、Content-Type ヘッダーにエンコーディングを指定できるのが望ましいと言えます。 その場合は、次のように記述します。

print $q->header( -charset => 'utf-8' );
print '<!DOCTYPE html><html lang="ja"><meta charset="utf-8"><title>-</title><p>こんにちは';

これで、Content-Type: text/html; charset=utf-8 というヘッダーを出力してくれるようになります。

header メソッドは、主な HTTP ヘッダーをサポートしており、引数として指定することが可能です。 次の例は、HTTP ステータスコードと Content-Type、そして、キャラクターセットを指定しています。

print $q->header(
    -status  => '404',
    -type    => 'text/plain',
    -charset => 'utf-8',
);
print 'リソースが見つかりませんでした。';

-status には HTTP ステータスコードを指定します。 上記では 404 だけを指定していますが、Not Found は自動的に付加してくれます。 もちろん、-status => '404 Not Found' と指定しても構いません。

上記で紹介したヘッダー以外に Set-Cookie ヘッダーが良く使われますが、 Cookie の扱いについては後述します。

リダイレクションヘッダーの出力

リダイレクションヘッダーの出力には redirect() メソッドを使います。

print $q->redirect( -uri => 'https://github.com/futomi' );

この場合、レスポンスの HTTP ステータスは 302 Found となります。 もし他のステータスコードでリダイレクトしたい場合は -status を使います。

print $q->redirect(
    -uri    => 'https://github.com/futomi',
    -status => '301' # Moved Permanently
);

Cookie を扱う

Cookie をセットする

次のサンプルコードは、CGI.pm を使って Cookie を生成してレスポンスヘッダーとして出力しています。

my $cookie = $q->cookie(
    -name  => 'nickname',
    -value => 'たろう'
);

print $q->header(
    -type   => 'text/plain',
    -cookie => $cookie
);
print $cookie;

cookie() メソッドは Set-Cookie ヘッダーの値を生成します。 そして、header() メソッドに -cookie パラメータを指定します。 このサンプルで出力される値 (cookie() メソッドが生成した値) は次の通りです。

nickname=%E3%81%9F%E3%82%8D%E3%81%86; path=/

ご覧の通り、日本語の値も自動的に URL エンコードしてくれます。 このサンプルでセットされる Cookie はテンポラリー (セッション) Cookie です。 もし有効期限をセットしたい場合は、cookie() メソッドにパラメータを追加します。 CGI.pm がサポートしているパラメータすべてをセットすると、次のようになります。

my $cookie = $q->cookie(
    -name      => 'nickname',
    -value     => 'たろう',
    -expires   => '+1h',
    '-max-age' => '+1h',
    -path      => '/lecture/form/exec',
    -domain    => '.futomi.com',
    -secure    => 1,
    -httponly  => 1,
    -samesite  => 'Lax',
    -priority  => 'High'
);

CGI.pm の Cookie 生成は、内部的には CGI::Cookie というモジュールの機能を使っています。 -samesite-priority は、 古いバージョンの CGI.pm がインストールされた環境では動作しない可能性がありますので注意してください。 各パラメータの意味は「CGI::Cookie の使い方」をご覧ください。

もし 2 つの Cookie をセットしたい場合は、次のように、cookie() メソッドによって 2 つの Cookie を生成し、header() メソッドの -cookie パラメータに配列として 2 つの Cookie を与えます。

my $cookie1 = $q->cookie( -name => 'nickname', -value => 'たろう' );
my $cookie2 = $q->cookie( -name => 'age',      -value => '30' );

print $q->header(
    -type   => 'text/plain',
    -cookie => [ $cookie1, $cookie2 ]
);
print 'OK';

Cookie を読み取る

Cookie の読み取りは、cookie() メソッドを使いますが、その使い方は param() と同じです。

Cookie 名を指定して値を読み取る場合は、cookie() メソッドの引数に Cookie 名を与えます。

my $nickname = $q->cookie('nickname');

ブラウザーから送信されてきた Cookie すべての Cookie 名を得たいなら、cookie() に引数を与えず、戻り値に配列を指定します。

my @names = $q->cookie();

スクリプトの URL を取得・分解する

CGI.pm には、CGI の URL を取得する機能が実装されています。 また、その URL を分解した形で取り出すことも可能です。 スクリプトの URL は url() メソッドを使って取得しますが、 与える引数に応じて得られる結果が異なります。

下表は、以下の URL でスクリプト (CGI) にアクセスしたときに得られる結果です。 実際に下記 URL にアクセスしてご確認いただくこともできます。

https://www.futomi.com/lecture/form/exec/cgipm_url.cgi/virtual/path?keyword=hello#chapter1

呼び出し方 結果
$q->url() https://www.futomi.com/lecture/form/exec/cgipm_url.cgi
$q->url( -full => 1 ) https://www.futomi.com/lecture/form/exec/cgipm_url.cgi
$q->url( -base => 1 ) https://www.futomi.com
$q->url( -absolute => 1 ) /lecture/form/exec/cgipm_url.cgi
$q->url( -relative => 1 ) cgipm_url.cgi
$q->url( -relative => 1, -path => 1 ) cgipm_url.cgi/virtual/path
$q->url( -relative => 1, -path => 1, -query => 1 ) cgipm_url.cgi/virtual/path?keyword=hello

-full => 1 は、URL 全体を表します。 url() は、引数に何も与えられていない場合は -full => 1 が指定されたとして処理します。

-base => 1 はスキームからドメイン名までを、 -absolute => 1 は絶対パスを、-relative => 1 は相対パスを表します。 相対パスは、事実上、CGI ファイル名になります。

-full => 1, -absolute => 1, -relative => 1 を指定した場合、オプションで -path => 1-query => 1 を追加で指定することができます。

-path => 1 は、CGI ファイル名の後ろに追加された仮想パスを指します。 上記では cgipm_url.cgi の後ろの /virtual/path が該当します。 -query => 1 はクエリを表し、上記では ?keyword=hello が該当します。

上記の例でお気づきかもしれませんが、url() メソッドはハッシュ (#chapter1 の部分) を扱うことはできません。