特集3̲2 04.1.21 20:26 ページ79

第 2 章
mod̲perl & モジュールと Apache，
MySQL で作る

実践編：ショッピング
カートの実装Tokyo Perl Mongers

小山浩之● OYAMA Hiroyuki ● oyama@cpan.org

本章では，第1 章で紹介したmod̲perl とモジュールを活用してショッピングカートを実装し，応答速度素から構成されます（図1）．
このうちデータベースの操作を要する要素は以下の2点です．

などを検証します．

¡カタログ（商品データ）

要求の定義¡レジスタ（注文データ）

ショッピングカートに要求する機能を以下に示しま

本章ではショッピングカートの利用者に関係する基本動作のみを実装します．注文を受けた後の処理や商す．

品データのメンテナンス機能は割愛します．

¡カタログからアイテムの一覧を表示¡カートにアイテムを追加

データベースの選定¡カートからアイテムを削除¡レジスタでカートのアイテムを注文

DBI を使用することで，オープンソースから商用のこのアプリケーションは商品を表す「アイテム」，商

ものまでさまざまなリレーショナルデータベースを操品情報を持つ「カタログ」，選択した商品を保持する

作することができます．既存のリレーショナルデータ「カート」，注文を受け付ける「レジスタ」の4 つの要

ベースを利用する場合は別ですが，新たにWebアプリ
ケーションを開発する場合，どのプロダクトを選定す●図 1 ショッピングカートの概要

れば良いのでしょうか．そのためにはまず開発する
Webアプリケーションの特性を見極める必要がありま

Item

す．
今回作成するショッピングカートのアプリケーショ

Cart

ンは，シンプルなテーブル構造でかつデータベースへの要求の大半は検索処理で占められます．またテーブ
Catalog

Registerル構造がシンプル故に，更新処理もシンプルになります．そのため重視するポイントは「検索処理の早さ」に絞ることができ，処理をアトミックに扱うトランザ

商品

受注

クションの優先度は低くなります．これはニュースサ

WEB +DB PRESS Vol.1 ● 79

特集3̲2 04.1.21 20:26 ページ80

Perl による ハイパフォーマンス
Web アプリケーションの開発

特集

3

Powered by mod_perl, Apache & MySQL

イトやコミュニケーションサイトなどにもあてはまる

覧や選択肢を定義したテーブルをアプリケーションか

条件と言えます．

ら参照し，表示するケースが多く見られます．この定

今回はマルチスレッドによる軽量で高速な検索と，義用のテーブルが多数ある場合には，データベースエ

堅牢なデータ保持を提供するMySQLを選定しました．

ンジンのテーブルキャッシュの消費量も増加します．

複雑なテーブル構造を扱う必要がある場合は，大量のトラフィックを受けるWebアプリケーションで※1などが候補にあがるでしょうし，保証

これら複数のテーブルを頻繁にアクセスする必要があされたサポートサービスが必要な場合は，サポートサる場合は，定義情報を単一のテーブルにまとめると，ービス業者が存在するプロダクトか商用のプロダクト

良いパフォーマンスが得ることができる場合がありまが選定対象になります．商用・非商用を問わず，プす．消費するテーブルキャッシュを抑えキャッシュのロダクトの選定に際してはその実績やサポート体制はヒット率を上げることでパフォーマンスアップを狙い

もちろんのこと，採用しているアーキテクチャと設計

ます．

PostgreSQL

理念を理解し，比較することで自然と適切なプロダク

基本部品の開発トを選定できるはずです．

データベース設計上のポイント
リレーショナルデータベースにおけるテーブル設計の手法については，書籍はもちろん，使用するリレー
ショナルデータベースのドキュメントで言及されているはずですのでここでは述べません．データベースの先に挙げた「アイテム」「カタログ」「カート」「レ
ジスタ」の4要素を実装したモジュールを作成します．
この段階では基本動作のみを実装し，GUIに関する機能は実装しません．

アイテム

設計において，正規化を行い冗長な情報や重複した商品を表すアイテムは名前・金額・写真・特徴・情報を排除することは基本中の基本と言えます．しか寸法の属性を持ちます．このアイテムのデータ構造をし，特定の状況ではパフォーマンスを上げるためにデ保持するモジュールItem.pm（リスト1）を作成しまータベースを冗長化させる必要が生まれます．

す．

データベースの冗長化

クラスメソッドnew( )は新しく商品のデータ構造をインスタンス化します．new( )の引数で指定したハッシ

頻繁に発生する要求に複雑なテーブルの結合が必要

ュは ̲initialize( )メソッドに渡し，解析した結果を属なケースでは，ディスクI/Oや計算量の増加が問題に性に割り当てます．その他id，name，price，photo，

なる可能性があります．そのため，検索しやすいようfeature，sizeは，それぞれの属性にアクセスするために必要な情報を結合したテーブルをあらかじめ作成しの属性アクセスメソッドです．

ておくことで，この問題を回避できる場合があります．ただしデータベースに冗長な要素を増やすことは，Item.pmは以下のように使用します．まず商品の属性をハッシュで与えてデータ構造を作成します．

データの更新処理に関する手続きが増え，そのパフォ
ーマンスは低下します．また冗長化の方法によってはuse Item;

一時的に古い情報を検索してしまう可能性がありま

my $item = Item->new(

す．この方法はデータのメンテナンスに関する手間よ

id

=> 1,

りも，検索性能が重要な場合に行います．

name=> '商品名',

price=> 1200,

また，Webアプリケーションでは商品カテゴリの一※ 1）参考 URL ： PostgreSQL

80● WEB +DB PRESS Vol.1

http://www.postgresql.org/，日本 PostgreSQL ユーザ会

http://www.jp.postgresql.org/

特集3̲2 04.1.21 20:26 ページ81

第2章

ショッピングカートの
実装
photo

=> 'http://hostname/image/image01.gif',feature => '製品の特徴',
size$item->price(960);# 2割引!

print $item->price, "¥n"; #¥960=> '20x30x10'
ショッピングカートはこの「アイテム」を受け渡し);ながら動作します．
new( )の戻り値は指定した属性のアイテム（商品）を表すオブジェクトです．各々の属性を参照する場合
は，引き数を指定せずに属性アクセスメソッドを呼び
出します．

カタログ
カタログはデータベースからアイテムを取り出します．全アイテムを取り出す処理と，指定したアイテムのみを取り出す処理をサポートします．カタログにア

print $item->name;

イテムを要求すると，データベースを検索し，検索結

print $item->photo;

果を元にアイテムを生成します．

この例では商品名と画像へのURLを表示します．属
性値を変更する場合は値を引き数に属性アクセスメソ

カタログを表すモジュールCatalog.pm（リスト2）を実装します．
Catalog.pmはDBIを使用して検索を行い，Item.pm

ッドを呼び出します．

を使用してアイテムのオブジェクトを返します．クラprint $item->price, "¥n";# ¥1,200

スメソッドnew( )はカタログを使用するために必要な●リスト 1 Item.pm
package Item;
use strict;
sub new{
my $class = shift;
my %args = @_;my $self = bless {name=> undef,
price => 0,
photo => undef,feature => undef,
size=> undef,}, $class;$self->_initialize(%args);$self;}
sub _initialize{
my $self = shift;
my %args = @_;foreach my $name (keys %args) {if($name eq 'id'){ $self->id($args{$name}) }elsif ($name eq 'name'){ $self->name($args{$name}) }elsif ($name eq 'price') { $self->price($args{$name}) }elsif ($name eq 'photo') { $self->photo($args{$name}) }elsif ($name eq 'feature') { $self->feature($args{$name}) }elsif ($name eq 'size'){ $self->size($args{$name}) }}$self;}
sub id{my $self = shift;
if (@_) { $self->{id} = shift }$self->{id};}sub name{
my $self = shift;
if (@_) { $self->{name} = shift }$self->{name};}
sub price{
my $self = shift;
if (@_) { $self->{price} = shift }$self->{price};}
sub photo{
my $self = shift;
if (@_) { $self->{photo} = shift }$self->{photo};}
sub feature{
my $self = shift;
if (@_) { $self->{feature} = shift }$self->{feature};}
sub size{
my $self = shift;
if (@_) { $self->{size} = shift }$self->{size};}
1;__END__WEB +DB PRESS Vol.1 ● 81

特集3̲2 04.1.21 20:26 ページ82

特集

3

Perl による ハイパフォーマンス
Web アプリケーションの開発
Powered by mod_perl, Apache & MySQL¡SQL のセット・実行

初期化を行います．new( )ではアイテムを識別する
アイテムID を受け取ります．アイテムIDが指定さを行います．

れていない場合はすべてのアイテムを検索して出力しhandle( )メソッドはデータベースハンドルを返しまます．アイテムIDを表す$item̲idは̲initialize( )メソ

す．まだデータベースに接続していない場合は，接続

ッドに渡し，検索対象の決定に使用します．

を行い新しいデータベースハンドルを返します（リス̲initialize( )メソッドでは

ト2-w）．
¡データベースへの接続

DBI のステートメントハンドルは属性アクセスメソ●リスト 2 Catalog.pm
package Catalog;
use DBI;use Item;use strict;
sub new{
my $class = shift;
my $item_id = shift;
my $self = bless {handle => undef,
state => undef,}, $class;$self->_initialize($item_id);$self;}

sub _initialize{
my $self = shift;
my $item_id = shift;
my $handle = $self->handle;
eval {
if (defined $item_id) {$self->state($handle->prepare(q{SELECT id, name, price, photo, feature, size
FROM catalog
WHERE id = ?
}));$self->state->execute($item_id);}
else {$self->state($handle->prepare(q{SELECT id, name, price, photo, feature, size
FROM catalog}));$self->state->execute;}};if ($@) { die $@ }$self;}

sub get_item{
my $self = shift;
my $state = $self->state;
return undef unless my $record = $state->fetchrow_arrayref;

82● WEB +DB PRESS Vol.1

return Item->new(id=> $record->[0],name=> $record->[1],price => $record->[2],photo => $record->[3],feature => $record->[4],size=> $record->[5],
);

q

}

sub close{
my $self = shift;$self->state->finish
if $self->state;$self->handle->disconnect if $self->handle;}

sub handle{
my $self = shift;unless ($self->{handle}) {eval {$self->{handle} = DBI->connect(]'dbi:mysql:webapp', 'user', 'password', {RaiseError => 1}) or die;};if ($@) { die "connect: $@" }}$self->{handle};}

sub state{
my $self = shift;
if (@_) { $self->{state} = shift }$self->{state};}

sub DESTROY{
my $self = shift;$self->close;}
1;__END__w

特集3̲2 04.1.21 20:26 ページ83

第2章

ショッピングカートの実装
ッドのstate( )に保持します．
get̲item( )メソッドはデータベースに問い合わせたカート結果を元に，新しいアイテムのオブジェクトを返すイ

カートは選択した商品を保持・削除し，その一覧

テレータです（リスト2-q）．すべてのアイテムを取を提供する機能を持ちます．カートの実装にはセッシ

り出し終えるとundefを返します．

ョン管理機能が必要です．Apache::Session を組み込

Catalog.pmを利用する場合はクラスメソッドnew( )で新しいカタログを生成してみ，Cart.pm（リスト5）を作成します．
Cart.pmは与えられたセッションIDを元に選択したアイテムを保持します．

use Catalog;
my $catalog = Catalog->new;
そこからget̲item( )メソッドでアイテムを1 つずつ

クラスメソッドnew( )は新しいカートを生成し，セッション情報を元にカートのアイテムを復元します．
引数として指定したセッションID は̲initialize( )メソッドに渡した後，Apache::Session によるセッション

取り出します．
while (my $item = $catalog->get_item) {push @items, $item;}

情報の復元に使用します．新しいセッションの場合は未定義のセッションID を指定します（リスト5-q）．
Apache::Session::DB̲Fileを使用して，dbmにセッション情報を格納します．使用するdbm のファイル

使い終わったカタログは行儀よく閉じておくと，データベースとの接続も解除されます．

名とロックファイルを作成するディレクトリを指定します．セッション情報を結び付けるハッシュには
session( )メソッドでアクセスします（リスト5-t）．

$catalog->close;

add̲item( )メソッドは引き数で指定したアイテムをこれで商品の一覧を提供する基本部品ができ上がりました．この時点でCatalog.pm とItem.pm の関係と使用方法さえ認識していれば，データベースから商品

カートに追加します（リスト5-w）．
delete̲item( )メソッドは引き数で指定した番号のアイテムをカートから削除します（リスト5-e）．

一覧を取り出し必要な情報を掲示することができます．

Cart.pm ではアイテムを保持するために配列を使用

例として商品名と金額の一覧をタブ区切りで出力すしており，この配列にアイテムを追加することでカーる例を示します（リスト3）．
検索対象のデータベースとテーブルを作成します．

トに追加，配列から削除することでアイテムの削除を行います．

今回はMySQL を使用するので，mysqladmin でデー

カートのアイテムの操作を図2 に示します．

タベース webappを作成し，mysql クライアント

item( )メソッドはカートが保持しているアイテムをでテーブル catalog

を作成します（リスト4）．デ

リストで返します（リスト5-r）．

ータベース webapp にはユーザ名 user ，パスワ
ード password で接続することにします．

●リスト 3 商品名と金額の一覧をタブ区切りで出力#!/usr/bin/perl
use Catalog;
my $catalog = Catalog->new;while (my $item = $catalog->get_item) {printf "%s¥t%s¥n", $item->name, $item->price;}__END__●リスト 4 webapp，catalog の作成> mysqladmin -u user -p create webapp
Enter password: xxxxxxxxxx
Database "webapp" created.
> mysql -u user -p webapp
Enter password: xxxxxxxxxx
mysql>
mysql> CREATE TABLE catalog-> (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,-> name VARCHAR(100), price INT,-> photo VARCHAR(100), feature VARCHAR(255),-> size VARCHAR(100)-> );Query OK, 0 rows affected (0.12 sec)

WEB +DB PRESS Vol.1 ● 83

特集3̲2 04.1.21 20:26 ページ84

特集

3

Perl による ハイパフォーマンス
Web アプリケーションの開発
Powered by mod_perl, Apache & MySQL●リスト 5 Cart.pm
package Cart;
use Apache::Session::DB_File;use strict;

my $target = shift;splice @{$self->session->{item}}, $target, 1;}

sub new{
my $class = shift;
my $session_id = shift;
my $self = bless {session => {}}, $class;$self->_initialize($session_id);$self;}

sub item{
my $self = shift;r$self->session->{item} = [] unless exists $self->session->{item};return (@{$self->session->{item}});}sub session{
my $self = shift;
if (@_) { $self->{session} = shift }$self->{session};}

sub _initialize{
my $self = shift;
my $session_id = shift;eval {
tie %{$self->session}, 'Apache::Session::DB_File',$session_id, {q
FileName=> '/usr/home/oyama/var/sessions.db',
LockDirectory => '/usr/home/oyama/var/lock/sessions',};};
if ($@) { die create_session: $@ }$self;}

sub add_item{
my $self = shift;
my @item = @_;push @{$self->session->{item}}, @item;}

sub id{my $self = shift;$self->session->{_session_id};}y

sub close{
my $self = shift;$self->{session} = {};}u

sub DESTROY{w
my $self = shift;$self->session->{last_access} = time;
untie %{$self->session};}sub delete_item{
my $self = shift;1;e __END__●図 2 カートのアイテムの操作
Item 1$self->session->{item}

Item 2Item 3pushItem 4$self->session->{item}->d2f

sp
lice

shift

Item 1$self->session->{item}->d0f

Item 1

Item 1

Item 2Item 2Item 3Item 2Item 3Item 4Item 4Item 4Item 584● WEB +DB PRESS Vol.1$self->session->{item}->d4ftItem 3$self->session->{item}->d2fi特集3̲2 04.1.21 20:26 ページ85

第2章

ショッピングカートの実装
カートからすべてのアイテムを削除する場合は，

my $register = Register->new($cart);

close( )メソッドを呼び出します（リスト5-u）．
セッション情報の保存はCartオブジェクトが消滅すorder( )メソッドは利用者の情報をハッシュで受けるとき，自動的に呼び出されるDESTROYメソッドに取り，カートの情報を元に注文情報としてデータベーよって行われます（リスト5-i）．

スに格納します（リスト7）．

セッション情報の保存は，ハッシュのトップレベルの値を変更したときのみに行われます．アイテムを格

注文情報を格納するテーブル order̲info を定義します（リスト8）．

納している$self->session->{item} は配列のリファレン
スを格納しており，配列の要素を操作してもリファレ

コントローラの開発

ンス自体は変化しません．そのためセッション情報を確実に保存させるために last̲access に時刻を書き込んでいます．

基本部品には部品間の関連やデータの受渡しに関するコードを記述していません．これらを結び付ける3

またid( )メソッドでセッションID を参照できます

つのコントローラを作成します．

（リスト5-y）．Cart.pmではセッションIDを カート¡catalog.cgi

ID と呼ぶことにします．

カタログから商品を取り出し，一覧を表示します．

レジスタ¡cart.cgi

レジスタはカートのアイテムを受け取り，注文を行います．精算に際してカートの他に利用者の住所・氏名・電話番号を受け取ることにします（リスト6，次

カタログから指定した商品をカートに追加して，一覧を表示します．
指定した商品をカートから削除して，残った商品

ページ）．
クラスメソッドnew( )はカートのオブジェクトを引数として受け取り，新しいレジスタを生成します．

●リスト 7 利用者の情報をハッシュで受け取る$register->order(
Address => '東京都新宿区西新宿パークタワー35F',
Name=> '小山浩之',
Phone=> '03-1234-5678',);●リスト 9●リスト 8 order̲info の定義> mysql -u user -p webapp
Enter password: xxxxxxxxxx
mysql>
mysql> CREATE TABLE order_info-> (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,-> session_id CHAR(32), item_id INT,-> item_name VARCHAR(100), item_price INT,-> address VARCHAR(255), name VARCHAR(100),-> phone VARCHAR(50)-> );Query OK, 0 rows affected (0.12 sec)

catalog.html<HTML><BODY><TMPL_LOOP name=ITEM_LIST><TMPL_VAR name=NAME><BR><TMPL_VAR name=PRICE><BR><IMG src="<TMPL_VAR name=PHOTO>"><BR><TMPL_VAR name=FEATURE><BR><TMPL_VAR name=SIZE><BR><A href="<TMPL_VAR name=ADD_CART_URI>">add cart</A><BR><HR></TMPL_LOOP><A href="<TMPL_VAR name=CART_URI>">show cart infomation</A><BR></BODY></HTML>WEB +DB PRESS Vol.1 ● 85

特集3̲2 04.1.21 20:26 ページ86

Perl による ハイパフォーマンス
Web アプリケーションの開発

特集

3

Powered by mod_perl, Apache & MySQL●リスト 6 Register.pm
package Register;use DBI;use strict;
sub new{
my $class = shift;
my $cart = shift;
my $self = bless {handle => undef,
state => undef,
items => [],}, $class;$self->cart($cart);$self;}

sub order{
my $self = shift;
my %customer = @_;$self->_check_customer_info(%customer)
or die "order: Wrang customer infomation";
my $handle = DBI->connect('dbi:mysql:webapp', 'root', 'test', {RaiseError => 1,# AutoCommit => 0,});
my $state = $handle->prepare(q{INSERT INTO order_info(session_id, item_id, item_name, item_price, address, name, phone)
VALUES (?, ?, ?, ?, ?, ?, ?)});eval {
my $session_id = $self->cart->id;
foreach my $item ($self->cart->item) {$state->execute($session_id,$item->id,$item->name,$item->price,$customer{Address},$customer{Name},$customer{Phone},);}};if ($@) {die "order: $@"}else {# $handle->commit;$handle->disconnect;}$self;}
sub _check_customer_info{
my $self = shift;
my %customer = @_;return undef unless defined $customer{Address};
return undef unless defined $customer{Name};
return undef unless defined $customer{Phone};return $self;}

sub cart{
my $self = shift;
if (@_) { $self->{cart} = shift }$self->{cart};}
1;__END__

86● WEB +DB PRESS Vol.1

特集3̲2 04.1.21 20:26 ページ87

第2章

ショッピングカートの実装を表示します．

cart.cgi¡register.cgi
注文に必要な利用者情報の入力欄を表示します．
入力欄に正しく記入した場合，カートの商品を注文します．

cart.cgi（リスト11）も同様に，テンプレート（リスト12）を元に動的にHTMLを出力します．
cart.cgiの処理の流れはcatalog.cgiと同じですが，セ注文が完了したら，カートの内容を空にします．

ッション管理を行うためにHTTP Cookie の操作を行います．

catalog.cgi

HTTP Cookie から取り出したカートID（セッショcatalog.cgiはHTML::Templateを使用してテンプレンID）をCart.pmのクラスメソッドnew( )に渡し，指ート（リスト9）を元に動的にHTML を作成します．

定してセッション管理の下でカートを生成します（リCatalog.pmからアイテムを取り出し，テンプレート

スト11-q）．
これにより以前のカートの状態は復元されます．

に値を埋め込んでいきます．
catalog.cgi（リスト10）はテンプレートを読み込み（リスト10-q）カタログからアイテムを取り出し，テンプレートへ挿入するデータを用意します（リスト

アイテムの追加や削除は，URIで指定したパラメー
タ mode の値によって行います．
パラメータ modeの値が addの場合，パラ10-w）．get̲ item( )メソッドでカタログからアイテ

メータ item

ムを取り出し，対応するテンプレートのタグに属性をから読み込みカートに追加します（リスト11-w）．サセットします．また，カートにアイテムを追加する

ブルーチンload̲item( )ではCatalog.pmを使用してア

URIを ADD̲CART̲URI で指定します．

イテムを読み込んでいます（リスト11-y）．

最後にヘッダと，テンプレートから作成したHTMLを出力します（リスト10-e）．

で指定したID のアイテムをカタログ

パラメータ mode
ラメータ indexの値が deleteの場合，パで指定した番号のアイテムをカートから削除します（リスト11-e）．
一覧を表示するためにテンプレートへ属性をセット●リスト 10

catalog.cgi#!/usr/bin/perl
use CGI;use HTML::Template;use Catalog;
use strict;
my $query = CGI->new;
my $html = HTML::Template->new(filename => 'catalog.html');
my $catalog = Catalog->new;
my $item_list = [];while (my $item = $catalog->get_item) {push @{$item_list}, {NAME=> $item->name,
PRICE=> $item->price,
PHOTO=> $item->photo,
FEATURE => $item->feature,
SIZE=> $item->size,
ADD_CART_URI => 'cart.cgi?mode=add'. '&item='. $item->id};}$html->param(ITEM_LIST => $item_list);$html->param(CART_URI => 'cart.cgi');
print $query->header;
print $html->output;

q

w

e

WEB +DB PRESS Vol.1 ● 87

特集3̲2 04.1.21 20:26 ページ88

特集

3

Perl による ハイパフォーマンス
Web アプリケーションの開発
Powered by mod_perl, Apache & MySQLする処理は，catalog.cgiと同様です．セッション情報

示するテンプレートリスト14と注文が完了した際に表を維持するためカートID を記録するHTTP Cookie を示するテンプレートリスト15 の2 つを使用します．

作成します（リスト11-r）．
最後にテンプレートから作成したHTMLを出力して終了します（リスト11-t）．

ォームを表示します．また，すでに入力された値を引き継 ぐため， H T M L : : T e m p l a t e のオプション

register.cgi

associate

register.cgi（リスト13）では，入力フォームを表●リスト 11

入力フォームの値をチェックし，未記入や誤入力がある場合はサブルーチンshow̲input̲form( )で入力フ

にCGI.pm のインスタンスを渡しています．associate オプションにCGI.pm のインスタンスを

cart.cgi#!/usr/bin/perl
use CGI;use HTML::Template;use Catalog;
use Cart;
use strict;

my $query = CGI->new;
my $cart = Cart->new($query->cookie(-name => 'session_id'));my $mode = $query->param('mode');if ($mode eq 'add') {my $item = load_item($query->param('item'));$cart->add_item($item);}elsif ($mode eq 'delete') {$cart->delete_item($query->param('index'));
}

q

w

e

my $html = HTML::Template->new(filename => 'cart.html');
my $item_list = [];my $index = 0;
foreach my $item ($cart->item) {push @{$item_list}, {ID=> $item->id,
NAME=> $item->name,
PRICE => $item->price,
DELETE_CART_URI => 'cart.cgi?mode=delete'. '&index='. $index++,};}$html->param(ITEM_LIST=> $item_list);$html->param(CATALOG_URI => 'catalog.cgi');$html->param(REGISTER_URI => 'register.cgi');
my $state_cookie = $query->cookie(-name => 'session_id',-value => $cart->id,-path => '/cgi-bin/',);print $query->header(-cookie => $state_cookie);
print $html->output;

sub load_item{
my $item_id = shift;
my $catalog = Catalog->new($item_id);
return $catalog->get_item;}

88● WEB +DB PRESS Vol.1

r

t

y

特集3̲2 04.1.21 20:26 ページ89

第2章

ショッピングカートの実装指定すると，同名のテンプレートへ自動的に入力値をメータをチェックして未入力の場合は *セットします（リスト13-e）．

ます（リスト13-r）．

入力もれのある箇所をマーキングするために，パラ●リスト 12を表示し住所・氏名・電話番号をすべて入力している時は，

cart.html<HTML><BODY><TMPL_LOOP name=ITEM_LIST><TMPL_VAR name=ID><TMPL_VAR name=NAME><TMPL_VAR name=PRICE><A href="<TMPL_VAR name=DELETE_CART_URI>">delete this item</A><HR></TMPL_LOOP><A href="<TMPL_VAR name=CATALOG_URI>">catalog</A><A href="<TMPL_VAR name=REGISTER_URI>">register</A></BODY></HTML>●リスト 13register.cgi#!/usr/bin/perl
use CGI;use HTML::Template;use Register;use Cart;use Item;use strict;filename => 'finish.html',
associate => $query);$html->param(CATALOG_URI => 'catalog.cgi');
my $expired_cookie = $query->cookie(-name=> 'session_id',-value => '',-path=> '/cgi-bin/',-expires => 0,);
print $query->header(-cookie => $expired_cookie);
print $html->output;

my $query = CGI->new;
my $cart = Cart->new($query->cookie(-name => 'session_id'));if ($query->param('address') and}$query->param('name') and $query->param('phone')) {q
store_order_information($query, $cart);$cart->close;
sub show_input_form
show_finish($query);{}
my $query = shift;else {
my $cart = shift;show_input_form($query, $cart);
my $html = HTML::Template->new(}filename => 'register.html',eassociate => $query);
my $item_list = [];my $index = 0;
sub store_order_information
foreach my $item ($cart->item) {{push @{$item_list}, {my $query = shift;
NAME=> $item->name,my $cart = shift;
PRICE => $item->price,
my $register = Register->new($cart);w
DELETE_CART_URI => 'cart.cgi?mode=delete'. '&index='. $index++,$register->order(
Address => $query->param('address'),};Name=> $query->param('name'),}Phone => $query->param('phone'),$html->param(ITEM_LIST => $item_list););$html->param(CATALOG_URI => 'catalog.cgi');$html->param(MARKING_ADDRESS => $query->param('address') ? '' : '*');}$html->param(MARKING_NAME => $query->param('name') ? '' : '*'); r$html->param(MARKING_PHONE => $query->param('phone') ? '' : '*');
sub show_finish
print $query->header;{
print $html->output;
my $query = shift;}
my $html = HTML::Template->new(

WEB +DB PRESS Vol.1 ● 89

特集3̲2 04.1.21 20:26 ページ90

特集

3

Perl による ハイパフォーマンス
Web アプリケーションの開発
Powered by mod_perl, Apache & MySQL

サブルーチンstore̲order̲information( )で入力した情応答速度の確認報とカートに含むアイテムを注文します（リスト13q）．
サブルーチンstore̲order̲information( )は，CGIと作成したコントローラはApache::Registryの環境下

カートのオブジェクトを受け取りRegister.pmのorderでの実行を前提に実装していますが，CGIの環境下で( )メソッドで注文情報を格納します（リスト13-w）．

も問題なく動作します．Apache ※2 にバンドルされて注文処理が完了した後はclose( )メソッドでカートのいるベンチマークツールApacheBench を使用して応情報を破棄し，サブルーチンshow̲finish( )で注文完了画面を表示します．またHTTP Cookie の有効期限を切り，セッション情報をブラウザに破棄させます．

答速度を計測してみます．

ApacheBench
ApacheBenchは指定したURLへ指定した平行度と回数でリクエストを発行し，その応答速度を計測しま
す．-cオプションでクライアントの数にあたる平行度，-●リスト 14

register.html<HTML><BODY><TMPL_LOOP name=ITEM_LIST><TMPL_VAR name=NAME><TMPL_VAR name=PRICE><A href="<TMPL_VAR name=DELETE_CART_URI>">delete this item</A><HR></TMPL_LOOP><A href="<TMPL_VAR name=CATALOG_URI>">catalog</A><FORM method="GET" action="register.cgi">
Address: <INPUT type="text" name="address" value="<TMPL_VAR name=ADDRESS>"><BLINK><FONT color="#FF0000"><TMPL_VAR
name=MARKING_ADDRESS></FONT></BLINK><BR>
Name: <INPUT type="text" name="name" value="<TMPL_VAR name=NAME>"><BLINK><FONT color="#FF0000"><TMPL_VAR
name=MARKING_NAME></FONT></BLINK><BR>
Phone: <INPUT type="text" name="phone" value="<TMPL_VAR name=PHONE>"><BLINK><FONT color="#FF0000"><TMPL_VAR
name=MARKING_PHONE></FONT></BLINK><BR><INPUT type="submit" value="submit"></FORM></BODY></HTML>●リスト 15

finish.html<HTML><BODY>
Thank you.<BR><TMPL_VAR name=address><BR><TMPL_VAR name=name><BR><TMPL_VAR name=phone><BR><A href="<TMPL_VAR name=CATALOG_URI>">catalog</A></BODY></HTML>※ 2）参考 URL ： Apache Project

90● WEB +DB PRESS Vol.1

http://httpd.apache.org/，Japanizaed Apache Server Project

http://www.apache.or.jp/

特集3̲2 04.1.21 20:26 ページ91

第2章

ショッピングカートの実装
tオプションで発行するリクエストの回数を指定します．
> ab -c 10 -t 100 http://localhost/cgi-bin/catalog.cgi

表1 はApacheBench を使用して，catalog.cgi の応が低いため，デザインや機能の変更や拡張に柔軟に対応することができます（図3）．
そしてmod̲perl のApache::Registry を使用することで，CGIの約30倍の応答速度を実現しました．
mod̲perlのドキュメント Introduction. Incentives.

答速度を計測した結果です．
結果Apache::Registry の環境で実行したWeb アプ

Credits.

※3にはmod̲perlを採用している著名なWeb

リケーションは，CGI の約30 倍の速度で実行するこサイトが紹介されています．それによるとMacromediaとができました．コンパイル結果と変数のキャッシン<http://www.macromedia.com/> では1 ヶ月のグを行わないApache::PerlRun の環境でも，スクリプ

利用者数 4,273,000 人， ValueClick<http://

トがコンパクトな場合は同等のパフォーマンスを得ら

valueclick.com/> では45 台のマシンで1 日に8,000

れることがわかります．

万 件 の ア ク セ ス ， MP3.com <http://www.
mp3.com/>では1日の利用者数408,000人にも上るト

まとめ

ランザクションを処理しているそうです．
これらの高トラフィックなWebサイトで採用され実データベースの操作などを行う「基礎部品」と，表際に運用されていることからも，PerlによるWebアプ

示するGUI を決める「テンプレート」，それらを操作

リケーション開発の優位性やmod̲perl のパフォーマ

する「コントローラ」の3要素でWebアプリケーショ

ンスの高さが証明できます．

ンを実装しました．一見取り扱うファイルが多く非効

そう，Perl のWeb アプリケーションは速いのです．

率的に見えるかもしれませんが，各々の要素の関連性◆●図 3 PAC 関連図
テンプレートPresentation
コントローラControl

基本部品

Abstract●表 1 catalog.cgi の応答速度
動作環境

1 秒間の処理件数

転送速度

mod̲perl（Apache::Registry）

20.64

14.64Kbps

mod̲perl（Apache::PerlRun）

15.95

10.93Kbps

CGI

0.76

0.53Kbps※単一のhtpd プロセスを対象に計測した結果※ 3）参考 URL ： mod̲perl guide: Introduction. Incentives. Credits. http://perl.apache.org/guide/intro.html

WEB +DB PRESS Vol.1 ● 91