Walrus,Digit. | 一覧 | 検索 | 更新履歴(RSS) | 新規作成
はてなブックマークに追加 はてなブックマークを表示 編集 | 編集(管理者用) | 差分

Perlメモ/CGI::Applicationモジュール

編集

Perlでの特に簡素なCGIフレームワーク。

ドキュメント

はてなブックマークを表示 はてなブックマークに追加 リンク 編集

CGI::ApplicationモジュールのPODドキュメント

CPANで最新のCGI::Applicationモジュールに埋め込まれているPODドキュメントを読むことができます。

CGI::Application 4.04版のドキュメントを和訳しました。4.06版までは、ドキュメントの内容に変更はありません。間違い等ありましたら、ぜひご指摘をお願いします。

CGI::ApplicationHTML形式POD形式原文(4.04版)

この他に、日本語の情報としては、川合さんの河馬屋二千年堂CGI::Applicationモジュール v.1.2 の和訳ドキュメントを見ることができます。上記の4.04版のドキュメント和訳にあたっても、参考にさせていただきました。

Tips

モジュールとCGIを1ファイルにまとめる。

CGI::ApplicationモジュールのSYNOPSISにあるサンプルでは、CGIファイルとモジュールを"webapp.cgi"と"WebApp?.pm"の2ファイルに分けています。

実際のアプリケーション作りではこうなっていたほうが(再利用性や柔軟性などから)良いと思いますが、ちょっとCGI::Applicationを試してみるなどという時には、ライブラリパスなどを考えなくて済むように、1ファイルにまとめたいと思うこともあります。この時は、単純に次のようにするだけで実現できます。

!#/usr/bin/perl

### 元"webapp.cgi"の内容...
package main;
# use WebApp;
my $webapp = WebApp->new();
$webapp->run();

### 元"WebApp.pm"の内容...
package WebApp;
use base 'CGI::Application';
# (一般的なケースではにsetup()はスキップ可能。以降のドキュメント参照。)
sub setup {
      my $self = shift;
      $self->start_mode('mode1');
      $self->mode_param('rm');
      $self->run_modes(
              'mode1' => 'do_stuff',
              'mode2' => 'do_more_stuff',
              'mode3' => 'do_something_else'
      );
}
sub do_stuff { ... }
sub do_more_stuff { ... }
sub do_something_else { ... }
1;

CGI::ApplicationモジュールのSYNOPSISと見比べてみてください。基本的には、webapp.cgiの内容をmainというパッケージに(明示的に)入れ、mainの"use WebApp?;"という行をコメント化し、つないだだけです。

もちろんこの後で、確認作業に満足しこれをベースに本格的に開発するとなったら、逆をしてやることもできます。webapp.cgiとWebApp?.pmという2ファイルに分け、webapp.cgiのコメント化した"use WebApp?;"という行の行頭の"#"を外せば、SYNOPSIS(を元にした形)に戻ります。

出力時の文字コード自動変換・ヘッダ自動設定

CGI::Application 3.31版ではcgi_prerun、cgi_postrunなど、様々なタイミングで共通処理を組み込める仕組みがあります。 これを利用して、スクリプト中では(あまり)文字コードを意識しなくてもすむようにすることもできます。

まず、CGI::Applicationを継承するクラスに、次のようなcgiapp_postrunメソッドを作成します。

sub cgiapp_postrun {
  my $self = shift;
  my $bodyref = shift;
  my $charset = uc($self->param('charset'));
  my $charmap = { 'ISO-2022-JP' => 'jis', 'SHIFT_JIS' => 'sjis', 'EUC-JP' => 'euc', 'UTF-8' => 'utf8' };
  if (defined($charset) and defined($charmap->{$charset})) {
    $self->header_add('-charset', $self->param('charset'));
    my $encode = $charmap->{$charset};
    my @lines = map { Jcode->new($_)->$encode } split(/\n/, $$bodyref);
    $$bodyref = join("\n", @lines);
  }
}

後は、このモジュールを利用するCGIスクリプトで以下の様にして出力キャラクタセットを指定するだけです。 キャラクタセットは、'ISO-2022-JP'(JIS)、'Shift_JIS'(シフトJIS)、'EUC-JP'(EUC)、'UTF-8'(Unicode)を指定できます。

$obj->param('charset' => 'Shift_JIS');

cgiapp_postrunは、HTMLデータが生成されて、HTTPヘッダが生成される直前に実行されます。 渡される引数はHTMLデータの文字列リファレンスです。 そこで、上記のいずれかのキャラクタセットが指定されていれば、cgiapp_postrunで以下を行っています。

  • HTTPヘッダ生成時に使われる引数に '-charset' => キャラクタセット を追加。
  • ボディを行単位で分割して、一行ずつキャラクタセットに対応する文字コードに(Jcodeモジュールで)変換。

この作業で、runで生成されたHTMLデータの文字コードが何でも、また複数の文字コードが混在していても、$obj->param('charset')に指定された文字コードに変換されて出力されるわけです。

なお、この方式を採るときは、以下の点に注意してください。

  • この方式でも、1行に複数の文字コードが混在するとお手上げです。そうならないように気をつけてください。
  • HTTPヘッダの他に、HTMLヘッダにも言語やキャラクタセットといった属性を与えることができます。
    • IEはHTTPヘッダに指定されているキャラクタセットを優先するようですが、それでもHTMLヘッダでもこれらを正しく指定するべきです。
    • CGIモジュールのstart_htmlでHTMLヘッダを生成する時は、 '-charset' => $charset, '-encoding' => $charset, '-lang' => 'ja-JP' という3組のパラメータを指定します。

技術的なメモ

CGI::Applicationの動作

CGI::Applicationは、以下の図の順番で、サブクラスのメソッドを呼び出します。 本図は、CGI::Application 3.31版に基づいています。

CGI::Applicationを使う利点の一つに、CGIファイルに設定、モジュールファイルにロジックと分けられることが挙げられます。 例えば、CGIファイル側でデータディレクトリをパラメータとして指定するようにし、モジュール側ではパラメータ値を参照する、といった形です。

この時、パラメータを設定するタイミングに注意が必要です。例えば、cgiapp_initやsetupはnewメソッドから呼び出されます。 このため、次のようなコードを書いても、この「data_dir」パラメータはcgiapp_initやsetupでは参照できません。 これらのメソッドで使用するパラメータであれば、newメソッドで指定しないと間に合わないわけです。

use CGI::Application;
my $cgi = CGI::Application->new;     # この時点でcgiapp_initやsetupも実行されている
$cgi->param('data_dir' => './data');
$cgi->run;

セッション管理

CGI::Application 3.21版以降では、CGI::Application::Plugin::Sessionモジュールを併用することで、セッション管理機能を組み込むことができます。

以下はサンプルです。 まず以下のソースを「SessionTest.cgi」として保存します。パーミッション、改行コードなどは適宜整えてください。

#!perl

use lib qw(.);

use SessionTest;
my $SessionTest = SessionTest->new(PARAMS => {'data_dir' => './data'});
$SessionTest->run();

同じディレクトリに、以下のソースを「SessionTest.pm」として保存します。改行コードは適宜整えてください。文字コードはUTF-8で化けないようにヘッダをいじってあります。

package SessionTest;

use base 'CGI::Application';
use CGI::Application::Plugin::Session;

sub cgiapp_init {
  my $self = shift;
  # Configure the session
  my $sess_dir = $self->param('data_dir') . '/session';
  die unless (-d $sess_dir);
  $self->session_config(
    CGI_SESSION_OPTIONS => [ ("driver:File", $self->query, {Directory=>$sess_dir}) ],
    COOKIE_PARAMS       => { -expires => '+24h', -path => '/', },
    SEND_COOKIE         => 1,
  );
}

sub setup {
  my $self = shift;
  $self->start_mode('main');
  $self->mode_param('mode');
  $self->run_modes(
    'main' => \&process_main,
  );
  $self->header_add( -type => 'text/html; charset=UTF-8' );
}

sub process_main {
  my $self  = shift;
  my $query = $self->query;
  
  # 訪問回数の取得とカウントアップ
  my $count = $self->session->param('times') + 1;
  $self->session->param('times' => $count);
  
  # コンテンツの出力
  my $meta_header = $self->query->meta({-http_equiv => 'Content-Type', -content => 'text/html; charset=UTF-8'});
  my $html_header = $self->query->start_html( '-head' => $meta_header, '-charset' => 'UTF-8', '-encoding' => 'UTF-8', '-lang' => 'ja',);
  my $html_body = "<h1>CGI::Application::Plugin::Session test</h1>\n表示回数 : ${count}回\n";
  my $html_footer = $self->query->end_html;
  return $html_header . $html_body . $html_footer;
}

1;

"./data/session"ディレクトリを作成した後、「SessionTest.cgi」に繰り返しアクセスすると、表示回数が増えていきます。

セッション関連の作業は、sub cgiapp_initメソッド内でセッションの設定をしているだけです。 これで、CGI::Applicationオブジェクトではsessionメソッドで現在のセッションを取得できるようになり、セッションIDなどは自動的にCookieで受け渡されるようになります。

コメント

[[#rcomment]]