Windows2000で動く 一番簡単なWEB+DB環境

〜Apache・ActivePerl・PostgreSQL連携〜

更新日 2002/12/12

[HOME] / [Java] / [Perl5] / [Palm(GCC)] / [Palm(CodeWarrior)] / [EJB] / [本の紹介]


1. はじめに

  最近,WEB+DB環境で実現するシステム開発が大半を占めるようになり,基本技術として技術者の必須項目になってくるなど私のような少し古めの技術者は焦りを感じているところですが,みなさんの中にも書籍は読んでいるんだが実践は,という方もいらっしゃるかと思います.
 この手のものは習うより慣れろで,とにかく動く環境が身近にあって,思いつくアイディアをこまめに動作確認できることがとても重要であると考えます.そこで,本稿では,日常使っているPC上に,ちょいとWebサーバ,データベースを載せて,片手間にPerlプログラミングできる環境を構築してみます.

 WEB+DB環境と言えば,今やサーブレットの感もあり,CGIやPerlが効率の悪さの代表格のような扱いを受けることもありますが,今回利用するmod_perlモジュールをApacheに組み込めば,レスポンスの遅さやCPU負荷の問題が基本的になくなります.また,正規表現でのパタンマッチングや置換の処理は初心者には分かりにくいですが,これこそがPerlの強みで,覚えていただきたい機能の1つです.今回は主に入力チェックに用いています.

 前半は,WEB+DB環境を構築するための各種インストールについて,つまづきそうなところを重点的に説明します.この中で,まずPerlからのDB操作について動作確認し,次にWebブラウザからURL指定で Apacheを通したWEB+DB環境全体の動作を確認していきます.
 後半は,構築した環境を利用して,郵便番号の検索を行う簡単なプログラムを通して,FORMからの入力の引渡しや正規表現を用いた入力チェック,データベース検索のやり方と結果の表示について解説します.

 なお,この内容は,技術評論社のWEB+DB Press vol.8 (2002年4月25日出版)に載せたものです.

   2.動作環境
   3.Apacheのインストール
   4.ActivePerlのインストール
   5.ActivePerlの日本語対応
   6.mod_perlのインストール
   7.Apache  httpd.confの設定
   8.
   9.

 13. 参考文献

2. 動作環境            ↑top
 通常,WEB+DBの環境は動作安定性の点から Linuxなどが選択されるのが一般的で,本誌で関連記事を探すのはたやすいことです.しかし,プログラミング初心者にとって,Linux環境の構築は少し敷居が高いように思います.また,1台のPCの場合,WindowsをLinuxに入れ替えては,日常に支障をきたすこともあるでしょうし,マルチOSするというでは,もう1つハードルが加わってしまいます.今回はWindowsPC1台でありながら,Webサーバ,Perl,そしてデータベース(PostgreSQL)を連携させるための動作環境を構築することにします.
 ここでは,Windows2000を搭載したPCを想定しています.この上に Webサーバ,Perl,データベースlなどを載せていく訳ですが,Apacheにしろ,Perlにしろ,Windows用にチューンされたものが既に用意されていますので,それらを利用することにします.
 インストールする際のWindowsユーザについてですが,Administratorで実施することをお薦めします.

 今回使用したソフトウェアのバージョンを以下に示します.Windows以外は,すべて無償でインターネットから入手することが可能です.

3. Apacheのインストール ( 詳細 )    ↑top
 Apacheのインストールを行います.まずは、 Apache Software Foundation から ダウンロード (apache_1.3.24-win32-x86-no_src.msi)してください。これをダブルクリックして、インストーラを起動しますが、一応 Administrator権限でインストールしてください.一般ユーザでも問題ないと思うけど、確認してないので。。途中,インストール先のフォルダを聞かれますので,C:\Apacheにしましょう.後々楽です.違うフォルダにインストールする場合は,後述のmod_perlのインストール先が合わせてください.また,mod_perlを動作させるために,後ほどC:\Apache\conf\httpd.confファイルに追加設定を行います.詳細を ここ に示します(ちょっとファイルが大きいです).
 
4.ActivePerlのインストール ( 詳細 )    ↑top
 ActivePerlは, ActiveState社のHP からダウンロードします.画面の中のRELEASESのActivePerl5.6.1build631のdownloadをクリックしてください.この際,ブラウザは,IE5以上という指定がありますので,それに従っといてください.詳細を ここ に示します(これもちょっとファイルが大きいです).
 
 
5. ActivePerlの日本語対応    ↑top
 日本語のコード変換を行うため,Jcodeモジュールをインストールします.その名も Jcode.pmというHPです .ファイルは ここ(Jcode-0.79.zip) です.C:\tempあたりで解凍して perl  win_install.pl を実行してください.エクスプローラでのダブルクリックでもインストールは可能ですが,この場合,以下に示す画面が瞬間に消えてしまいます.


 
6. mod_perlのインストール   ↑top
 通常CGIは、 Webブラウザからのperlスクリプト実行の要求が来るたびに,ApacheなどのWebサーバソフトが perlを起動し,該当のperlスクリプトをコンパイルし,それを実行して回答が返却しているため,応答時間がかかったり,サーバの負荷を増やすといった問題が指摘されてきました.

 mod_perlは,Apacheモジュールの1つで,Perlで書いたスクリプトをApacheのモジュールとして組み込むためのものです.今回は,この中のApache::Registryという機能を使用していますが,この機能は,Perlスクリプトを最初の要求時に一度だけコンパイルしてメモリ上に常駐(キャッシング)することで,CGIで問題になる実行時のオーバーヘッド(perlの起動および該当スクリプトのコンパイル)をなくす働きをします.メモリ常駐というと、Perlスクリプトを修正した場合のメモリへの反映が気になりますが,mod_perlではファイルの更新を検出して再度コンパイルされるため,Webサーバを動作させたままスクリプトを変更することが可能です.

 ActivePerlのインストールが終わりましたら,引き続いてmod_perlをインストールします.インターネットにつなげた状態で,以下を実行してください.ppmコマンドも,インストール時にPATH設定されていますのでどこで実行しても構いません.ここでは,コマンドプロンプトを起動した直後に,ppmコマンドを入力しています.
  C:\> ppm install http://theoryx5.uwinnipeg.ca/ppmpackages/mod_perl.ppd

 このとき,上記画面とは別に以下のような画面が表示されますので,インストール先のフォルダを入力します.C:\Apacheにインストールした人は,そのままリターンしてください.ほら後々楽でしょ.入力に間違いがなければ,これでインストールは終わりです.間違った場合は,Create it? [no]と出ますので,そのままリターンを押して一旦インストールを終了し,再度 ppmコマンドを実行してください.

 例. Which directory should mod_perl.so be placed in?
         (enter q to quit)  [C:/Apache/modules]


 
7. Apache httpd.confの設定    ↑top
 mod_perlがインストールされたところでApacheの設定ファイル(httpd.conf)に以下を追加します.この設定により,Alias で示した perl配下のスクリプトに対して,mod_perlが有効になります.ここでは,Apache::Registryという設定をしています.httpd.conf 変更後に,Apache.exeを再起動してください.Apacheは,スタートメニューからでもサービスからでも起動・終了が可能です.

C:\Apache\conf\httpd.conf  ファイル


LoadModule perl_module modules/mod_perl.so

Alias /perl/ "C:/Apache/perl/"

<Location /perl>
SetHandler  perl-script
PerlHandler Apache::Registry
PerlSendHeader on
Options +ExecCGI
</Location>

 
 
8. mod_perl動作確認    ↑top
 それでは,サンプルプログラムを使って動作の確認をしてみましょう. ここでは,Apacheインストール時に作成された cgi-bin 配下の printenv.pl を用います.内容は以下に示すもので,各種環境変数を表示するというものです. C:\Apache\perl配下にコピーして,拡張子を .pl から .cgi に変更してください.ActivePerlインストール時にパス設定がなされていますが,もし動かないようであれば,最初に以下を追加してください.

【リスト printenv.pl】
#!C:/perl/bin/perl

サンプルプログラム printenv.cgi
#!C:/perl/bin/perl
print "Content-type: text/plain\n\n";
foreach $var (sort(keys(%ENV))) {
    $val = $ENV{$var};
    $val =~ s|\n|\\n|g;
    $val =~ s|"|\\"|g;
    print "${var}=\"${val}\"\n";
}

 Webブラウザを起動して,http://localhost/perl/printenv.cgi と入力してください.以下のような画面が表示されれば,Apache,mod_perl,ActivePerlまでの連携がうまく動作していることになります.


 
9. PPMとは?    ↑top
 PPMというのは,ActivePerlのモジュール管理用ツールのことで,コマンドプロンプトからppmと入力することで起動します.

C:\>ppm
PPM interactive shell (2.1.5) - type 'help' for available commands.
PPM>

ここで,helpと打てばコマンド一覧が表示されますが,以下に主要なもの示します.
install  インターネットあるいはローカルファイルからモジュールをインストールします.
query      ppmによりインストールされたモジュール名とバージョンを表示します.
exit     ppmを終了します.quitでも同様です.

本文では,4つのモジュールをこのppmを使ってインストールしています.DBD::Pgのみローカルファイルからのインストールになります.
DBI, DBD::Pg , Apache:DBI については、11章に具体的に手順を説明しているので、そちらを参照ください。

mod_perl        C:\> ppm install http://theoryx5.uwinnipeg.ca/ppmpackages/mod_perl.ppd
DBI               C:\> ppm install DBI
DBD::Pg         C:\> ppm install --location=. DBD-Pg
Apache::DBI    C:\> ppm install http://theoryx5.uwinnipeg.ca/ppmpackages/Apache-DBI.ppd

インターネットからの install がうまくいかない場合,プロキシサーバを経由している場合がありますが,このときは以下を実施してから,ppmコマンドを起動させてください.(proxy-server-nameやポート番号8080は環境に合わせてください)

C:\>SET HTTP_Proxy=http://proxy-server-name:8080/

この他,ActiveSTATEが提供しているモジュールは,以下のURLで一覧を見ることができます.
 http://www.activestate.com/PPMPackages/zips/6xx-builds-only/
 
 
10. PostgreSQLのインストール   ↑top

 PostgreSQLについても,Windowsネイティブ版が提供されていますのでこれを利用します.
WindowsでのPostgreSQL環境には Cygwin を使ったものもありますが,今回は少しでも敷居が低くなるように,よりインストールが容易な方を選択しました.いくつか制約がありますが,今回のような目的にはとても使いよいものです.

 「PostgreSQL Windows-Native サーバー & クライアントパッケージ」 のURLを以下に示します.
   http://hp.vector.co.jp/authors/VA023283/PostgreSQL.html

 「Download New Release 7.2」 と書かれたところからダウンロードします.同じところに,前バージョンも用意されていますが,今回は最新のものを使います.ファイル名はpostgresql-7_2_win32_bin_rv02.zipです.

  取得したzipファイルを解凍し,フォルダごと  C:\ 直下に移動します.initdb.exeをダブルクリック して終わり.コマンドプロンプトからも実施しても同じですが,2度実施してはいけません.このとき initdbを実施したWindowsアカウントがPostgreSQL管理者名およびデータベース名になるようです.図は,egami アカウントで作成した例で,PostgreSQL単体としての動作は問題ありませんが,他との連携を考慮して Administratorアカウントで作成されることを推奨しておきます.

PostgreSQLの起動は,pgsvmgr.exeをダブルクリックして,Startupをクリック.コマンドプロンプトから起動しても以下の画面が出るので,同様にStartupをクリックすればいいでしょう.

 ここで使用させていただいた postgresql-7.2-win32は,NativeなWinNT4,2000,XPで動作する開発・スタディ専用のPostgreSQLパッケージということで,インストール説明からも分かるように非常に手軽に導入することができます.
 開発用ということで,以下に示すいくつかの制限事項がありますが,今回のようなWEB+DB環境におけるデータベース設定や,Perlスクリプトの開発を目的とした場合,十分すぎるほどの仕様と言えます.

【PostgreSQL Windowsネイティブ版の制約事項】
 1. 展開させたディレクトリ構成を変更しないこと.
 2. Postgresスーパーユーザーは初期化(initdb)時ユーザーになる.psqlなどはこのユーザで操作すること.
 3. サーバーエンコーディングは,EUC_JP固定.
 4. マルチセッションおよびトランザクションブロックに対応していないため,スダンドアローン・1ユーザーで使うこと.
 
 
11. DBI・DBDのインストール  ↑top

 DBIは,Perlからデータベースを操作するためのインタフェースを定義した共通APIで,異なるデータベースへのアクセスを統一的に記述することを可能にするものです.これに対し,DBDは各種データベースに接続するためのドライバで,実際に接続するデータベースに合わせてインストールする必要があります.
 DBIのインストールは,mod_perlと同様で,ppmコマンドを用いて実施します.インターネットに接続した状態で,コマンドプロンプトより以下を入力します.
C:\> ppm install DBI
Installing package 'DBI'...
Bytes transferred: 173263
Installing C:\Perl\site\lib\auto\DBI\dbd_xsh.h 
Installing C:\Perl\site\lib\auto\DBI\DBI.bs
Installing C:\Perl\site\lib\auto\DBI\DBI.dll

Writing C:\Perl\site\lib\auto\DBI\.packlist

 加えて,Apache::DBIもインストールします.これは,一度データベースへ接続した状態を保持することで,その後のデータベース処理の高速化を図るものです.スクリプトへの記述は,第2章で扱うこととします.

C:\> ppm install http://theoryx5.uwinnipeg.ca/ppmpackages/Apache-DBI.ppd

 次に,DBDのインストールですが,PostgreSQL用のDBD(DBD::Pg)が必要になります.このDBD::Pgは
通常のモジュールとは別の場所にありますので,以下から取得してください.
 http://www.edmund-mergl.de/export/DBD-Pg.zip  

取得したZIPファイルの解凍は,C:\tempの配下にしておきます.解凍すると, DBD-Pg というフォルダが作成され,その配下にDBD-Pg.ppdとREADMEが入ってます.READMEはNotePadなどで見ると

To install this PPM package, run the following command
in the current directory:
    ppm install --location=. DBD-Pg

と書いてあるのでそのとおりにします.C:\tmpで解凍したのであれば,C:\tmp\DBD-Pgに移動して,以下を実行してください. このとき,= の後の .(ピリオド)に注意してください

C:\> ppm install --location=. DBD-Pg
Installing package 'DBD-Pg'...
Installing C:\Perl\site\lib\auto\DBD\Pg\Pg.bs
Installing C:\Perl\site\lib\auto\DBD\Pg\Pg.dll
Installing C:\Perl\site\lib\auto\DBD\Pg\Pg.exp
Installing C:\Perl\site\lib\auto\DBD\Pg\Pg.lib
Installing C:\Perl\site\lib\DBD\dbd-pg.pod
Installing C:\Perl\site\lib\DBD\Pg.pm
Writing C:\Perl\site\lib\auto\DBD\Pg\.packlist
 
12. DBI動作確認   ↑top

 それでは,先にインストールしたDBI,DBDを使って,データベースにアクセスするプログラムを動かしてみましょう.以下に示すリストをコピーしてファイル名 db_test.pl として作成してください.

【リスト db_test.pl】
use strict;
use Jcode;
use DBI;

my $dbh = DBI->connect(                             // @
 "dbi:Pg:host=localhost;dbname=Administrator",
 "Administrator","",
 {RaiseError=>1, AutoCommit=>0}) or
 die "Connect Error $DBI::errstr";

my $sth = $dbh->prepare( 'select * from meibo' );  //A
$sth->execute();                                                //B

while ( my @row = $sth->fetchrow_array()) {           //C
  print join("\t", $row[0], jcode($row[1])->sjis), "\n";  //D
};
$sth->finish();
$dbh->disconnect();

 まず,データベースへの接続を行った上で(@),検索の準備をし(A),実行しています(B).これで取得した検索結果を、1レコード毎取り出して$row[0],$row[1]…といった配列を表す@rowに代入し、レコードがなくなるまで処理を繰り返します(C).ここでは,$row[0]に id,$row[1]にnameが代入されますが,$row[1]は,EUCコードですので,sjisに変換処理して表示しています(D).このスクリプトを実行すると,先ほどPostgreSQLの動作確認用に作成したテーブルの内容が表示されるはずです.Jcodeモジュールは,EUCからsjisへの変換にのみ使っていますが,この他の使用方法については以下を参照ください. 
  http://openlab.ring.gr.jp/Jcode/Jcode.html

 ここまでに既に,Apacheからのperlスクリプトの実行と,perlからのDB検索について動作確認を済ませていますので,何も通しで試験する必要もないのですが,第1部の締めくくりとして,ブラウザからDB検索を実施してみます.

 さきほどの db_test.pl を C:\Apache_1.3.2-win32\perl 配下にコピーして,拡張子を .pl から .cgi に変更してください.これに以下にように print文を1つ追加するだけです.
 ブラウザから, http://localhost/perl/db_test.cgi と入力して,検索結果が表示されればすべてが動作したことになります.

【リスト db_test.cgi (db_test.pl を一部修正)】
use strict;
use Jcode;
use DBI;

my $dbh = DBI->connect(                             // @
 "dbi:Pg:host=localhost;dbname=Administrator",
 "Administrator","",
 {RaiseError=>1, AutoCommit=>0}) or
 die "Connect Error $DBI::errstr";

my $sth = $dbh->prepare( 'select * from meibo' );  //A
$sth->execute();                                                //B

print “Content-type: text/html\n\n”;       // これを追加
while ( my @row = $sth->fetchrow_array()) {           //C
  print join("\t", $row[0], jcode($row[1])->sjis), "\n";  //D
};
$sth->finish();
$dbh->disconnect();
 

【実行結果】

 
13. 異常時の対処   ↑top
 Perlスクリプト作っていると,よく,以下のような画面が表示されます.たいていは,プログラムミスによるものですが,初めてのときはどうしていいか分かりません.

 最後の行に server error  log を見なさいといっているので素直に見ます.ログは, C:\Apache\logs\error.log にあります.

[Sun Feb 24 21:28:17 2002] [error] Unrecognized character \x81 at c:/apache/perl/db_test.cgi line 14.
とあります.どうやら全角の空白が入っていたようです.

 ここまでは,主にWEB+DB環境のインストールと動作確認について説明してきました.皆さんのPCでも問題なく動いているでしょうか.
 引き続いて,この環境を利用して,少し実用的(?)なサンプルプログラムを元に,Perlスクリプトによるデータベース検索についてみていきます.
 
14. 郵便番号検索アプリケーションの開発  ↑top
 それでは郵便番号検索をWebブラウザから利用するためのサンプルプログラムを元に,入力情報の取得や正規表現を用いた入力チェック,データベース検索のやり方と結果の表示について考えてみましょう.

 今回取り上げる郵便番号検索は,郵便番号から住所を知りたい,あるいはその逆といった日常生活でも想定できる内容で画面の入出力がイメージしやすいこと,郵便番号と住所の組み合わせからなる単純なデータ構造であること,データがインターネットで公開されていて新たに作成する必要がないこと,などサンプルとしては適当かと思い選定しました.

 ユーザインタフェースはお世辞にもいいとは言えませんが,必要最小限という意味で,郵便番号の頭3桁以上,あるいは住所(カタカナ)の一部を入力することにより,郵便番号と住所(漢字)の一覧を表示させるものを想定します.ここでは,情報のデータベースへの登録や,入力画面,データベース検索,および結果の一覧表示といった形で説明していきます.
 
15. 郵便番号データのDB登録   ↑top
 仕様を実現させるために,テーブルに必要なカラムは,以下の3つと考えられます.
  郵便番号,住所(漢字),住所(カタカナ)

 これをもとに,データベース上に郵便番号テーブル(yuubin)を作成します. psql を起動し,以下を実行します.

   Administrator=# create table yuubin ( zipcode  text,  address  text, address_kana  text );
 

 郵便番号と住所の対応データは,「ゆうびんホームページ」 (http://www.post.yusei.go.jp/newnumber/) から取得します.ここで,「住所の新郵便番号ダウンロードサービス」 をクリックし,「全国一括(1,708,338Byte)」を取得します.ここでは,読み仮名データの促音・拗音を小書きで表記しないもの(例:ホツカイドウ)を使います.

提供されているデータは以下のような様式になっています.

 01101,“064  ”,“0640941”,“ホツカイドウ”,“サツポロシチュウオウク","アサヒガオカ","北海道","札幌市中央区","旭ケ丘",0,0,1,0,0,0
 

 このデータを先の郵便番号テーブルに登録するために,3カラム目を 郵便番号(zipcode)に,7?9カラム目をくっつけて住所(address),3?5カラム目をくっつけてカナ住所(address_kana)としてまとめることになります.
また,データの登録には,pslqの \copyコマンドを用いますが,各データ間をTAB(¥t)区切りで表現しておく必要があります.

これを実現するために,Perlでプログラム(zip_address.pl)を組んでみましょう.

【リスト zip_address.pl】
open( FILE, “KEN_ALL.CSV” ) or die “ファイル(KEN_ALL.CSV)が開けません”; // @
while (<FILE>) {        // A
  s/"//g;                // B
  @row = split /,/;    // C
  printf "%s\t%s%s%s\t%s%s%s\n", $row[2], $row[6], $row[7], $row[8], $row[3], $row[4], $row[5];   // D
}

  KEN_ALL.CSVというファイルを開きます.このときファイルが存在しなければ,die以下が表示されプログラムは終了します(@).開いたファイルの1行ごとについてwhileループを実行します(A).1行の中のダブルクォーテーション “ を何もない状態に置き換えます.つまり削除します.最後のgは一行ででてくるすべてについて実行することを示しています(B).これで上記の1行は以下のようになります.

 01101,064  ,0640941,ホツカイドウ,サツポロシチュウオウク,アサヒガオカ,北海道,札幌市中央区,旭ケ丘,0,0,1,0,0,0

 カンマ(,) を区切り文字として,1行を分解し,1つ目を$row[0],2つ目を$row[1]というぐあいに配列に代入します(C).その後のprintf文で所要の様式に整形しています(D).結果を以下に示します.

0640941 北海道札幌市中央区旭ケ丘  ホツカイドウサツポロシチュウオウクアサヒガオカ    ← データ間はTAB区切り

コマンドプロンプトにて上記Perlスクリプトを実行します. 
C:\> zip_address.pl > zip_address.txt
これを,psql の \copy コマンドで yuubin テーブルに登録します.
Administrator=# \copy  yuubin  from  zip_address.txt
 
16. 入力画面の作成    ↑top

 仕様に基づいて,入力画面を作成します.検索ボタン実行時に,search.cgiが呼ばれるように設定しています(@).これについては後で説明します.郵便番号についてのラジオボタンおよびテキスト領域を表示します(A).住所についても同様です(B).ここで,郵便番号が選択された場合,変数koumokuの値が“zipcode”に,住所が選択された場合では“address”という文字列になります.テキスト領域はそれぞれ変数zipcode,変数addressに入力文字が入ります.ここで使われている<table><tr><td>などは画面表示の体裁をつけるためで特に必要ありません.

【リスト input.html】

<html><head>
   <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
   <title>郵便番号検索の実験</title>
</head><body>

<FORM METHOD="POST" ACTION=“/perl/search.cgi">入力項目を選択し,該当の検索キーを入力してください.<BR>   ・・・@
  <table>
  <tr>
      <td><INPUT TYPE="radio" NAME="item" VALUE="zipcode" CHECKED>郵便番号</td>       ・・・A
      <td><INPUT TYPE="text" NAME="zipcode" > (例.285-0845, 950)</td>
    </tr>
    <tr>
      <td><INPUT TYPE=“radio” NAME=“item” VALUE=“address”>住所(カタカナ)</td>  ・・・B
      <td><INPUT TYPE=“text” NAME=“address”> (例.カドマ)</td>
    </tr>
  </table><br>
  <input type=submit value=“検索”>
</FORM>
</body></html>

【画面イメージ】

 
 
17. 郵便番号検索 Perlスクリプトの作成   ↑top
 入力画面で検索ボタンをクリックすると、search.cgiというPerlスクリプトが呼ばれることになりますが,これがPart2のメインイベントです.実際のスクリプトに沿って流れを説明すると,まず,入力画面からの情報を取得し(@),入力文字のチェックを行います(A).その後データベース検索し(B),結果を画面表示して(C),データベースの終了処理を行う(D)というものですが,それぞれの処理はサブルーチン化していますので内容については順を追って説明していくことにします.実際に動かすときは,このあとに続くサブルーチン部分もくっつけて,search.cgiというファイルに編集してください.

【リスト 郵便番号検索メインルーチン(search.cgi 1/5)】

#!C:/perl/bin/perl.exe

use strict;
use CGI;
use Jcode;
use DBI;
use Apache::DBI;

print "Content-type: text/html\n\n";
print "<html><head><title>検索結果</title></head><body>";

(my $koumoku, my $input) = input();  ・・・@
my $key = check($item, $input);  ・・・A
(my $dbh, my $sth) = db_search($item, $key); ・・・B
result($item, $input, $sth); ・・・C
$sth->finish(); $dbh->disconnect();   ・・・D

print "</body></html>";
 
18. CGI.pmによる入力情報取得   ↑top

  入力情報の取得についてですが,簡略化のためCGI.pmというモジュールを使います.今回構築した環境はCGIそのものではなく,mod_perlを用いてCGIでのPerlスクリプトをほぼ同じ形で,高速処理するという,いわば擬似CGI環境です.ここにCGIと名の付くモジュールを登場させると,いささか混乱を招くように思いますが,このモジュールはFORMからのPOST要求などに対して,入力内容の取得を容易に実現するものです.
 先ほど説明した画面で入力された情報の取得方法を具体的に説明してみます.

【リスト 入力情報の取得(search.cgi 2/5)】

sub input {
  my $query = new CGI;  ・・・@
  my $item = $query->param('item'); ・・・A
  my $input;

  if ($item eq "zipcode") {
    $input = $query->param('zipcode');  ・・・B
  } elsif ($item eq "address") {
    $input = $query->param('address');
  }
  return $item, $input;
}

 まず,CGIオブジェクトを生成しこれを$queryに代入します(@).こうすることで,ラジオボタン(item)で選択した項目(A)やテキストとして入力した文字列(B)を取得することができます.ここでは,選択項目と入力文字列をそれぞれ $item, $inputとしてリターンしています.
 
19. 正規表現による入力チェック   ↑top
 ここでは,先ほどの $item と $inputを引き渡して(@),入力チェックをした後、OKであれば加工した文字列を返却します.入力チェックにPerlの正規表現を用いることは冒頭で述べましたが,具体的に示していきます.

【リスト 入力チェック(search.cgi 3/5)】

sub check {
  my $item = shift;  ・・・@
  my $input = shift;

  if ($item eq "zipcode") {
    $input =~ s/-//;  ・・・A
    if ($input =~ /^$/) { ・・・B
      print "郵便番号が入力されていません.";
      Apache::exit;    ・・・C
    } elsif ($input !~ /^[0-9]{3,7}$/) {  ・・・D
      print "郵便番号の入力が間違っています.";
      Apache::exit;
    }

  } elsif ($item eq "address") {
    if ($input =~ /^$/) {
      print "住所が入力されていません。";
      Apache::exit;
    } elsif ($input =~ /[<>&"';]/) {    ・・・E
      print "住所の入力に誤りがあります.";
      Apache::exit;
    } else {
      $input = jcode($input)->z2h; ・・・F
    }
  }
  return $input;
}

 まず,郵便番号の場合ですが、途中で入るハイフン(-)を削除します(A).これは,データ登録したときの郵便番号がハイフンを含まない形で表現されているためです.Aの記述は一般には,$input =~ s/置換前の文字列/置換後の文字列/; というように使われ,この後ろにgをつけると,1行全てに対して置換処理が実施されます.ここでは,ハイフンが1つ入るものと想定して,1つめのみ置換(この場合削除)が実行されます.この$inputに対し,正規表現によるパタンマッチング機能を用いて,何も入力されていない状態を検出します(B).ここで,/^$/ は正規表現で,^は文字列の先頭,$は終わりを示しており,全体で何もないことを示しています.
 入力チェックには関係ありませんが,mod_perlを使っているときの注意点として,exitを使わないこと,というものがあります.mod_perlを使用した場合,最初にPerlスクリプトをコンパイルしてキャッシュし,その後は使いまわされることを説明しましたが,このときにexitを使うと,誤動作の原因になるということです.ここでは,Apache::exitを用いています(C).
 次に,数字のみで3〜7桁で表現されていることをチェックします(D).[ ]はその中の文字のどれか1つを示し,[0-9]と書くと,0,1,2…9つまり1桁の数字を示します.この後に続く{3,7}は,3〜7文字であることを示し,全体で数字3〜7桁であることを表現しています.ここでは,パタンマッチしないことを条件としているため,Bでの =~ ではなく,!~ を用います.
 住所(カタカナ)についても同様に未入力のチェックを行った後、画面表示やデータベース検索の際にセキュリティ上の問題になりそうな文字 <>&"'; を検出して(E),入力異常の画面を表示しています.最後にjcodeを使って半角カナに変換していますが(F),これは,データベース登録した住所(カタカナ)が半角カナで表現されているためです.
 
20. データベース検索   ↑top

 郵便番号の頭3桁を指定して,データベース検索を行う際のSQL文は以下のようになります.後ろの order by 節は郵便番号の小さいもの順にソートすることを示しています.psqlで動作を確認してみてください.

  select * from yuubin where zipcode like '285%' order by zipcode;

 これを元に,住所(カタカナ)をキーとした検索も含めた処理を記述すると以下のようになります.ここでは選択項目 $と検索キーとなる入力文字列 $keyを引き継ぎ(@),検索結果 $sth をリターンします.ここでは,メインルーチンでのデータベース切断のために データベースハンドラ $dbh もいっしょに返却しています(F).

【リスト データベース検索 (search.cgi 4/5)】

sub db_search {
  my $item = shift; ・・・@
  my $key = shift;
  my $sth;
  my $dbh = DBI->connect(  ・・・A
    "dbi:Pg:host=localhost;dbname=Administrator","Administrator","")
        or die "データベースに接続できません.$DBI::errstr";

  if ($item eq "zipcode") {
    $sth = $dbh->prepare( "select * from yuubin where zipcode like ? order by zipcode" ); ・・・B
    $sth->execute("$key%"); ・・・C

  } elsif ($item eq "address") { 
    $sth = $dbh->prepare( "select * from yuubin where address_kana like ? order by zipcode" ); ・・・D
    $sth->execute("%$key%"); ・・・E
  }
  return $dbh, $sth; ・・・F
}

 まず,localhost にある PostgreSQL に対してデータベース名Administrator,ユーザ名Administratorで接続します(A).そして,prepare()を用いてSQL文を埋め込み(BD),検索を実行します(CE).

 ここで,BCでのデータベース検索におけるセキュリティについて少し触れておきます.冒頭のSQL文を素直に実現すると以下のような処理になりますが,ここで $input に(A)のような内容が入力された場合を考えてみましょう. 

  $sth = $dbh->prepare("select * from yuubin where zipcode like '$key%'" order by zipcode"); B'
  $sth->execute(); ・・・C'

 $key = “123'; drop table yuubin; select * from yuubin where zipcode like '123"; ・・・(A)

このとき,B'は以下のように解釈されるとすると.. いったい,どうなるでしょうか.ちなみに,drop tableはテーブル削除のSQL文です.

 $sth = $dbh->prepare( "select * from yuubin where zipcode like '123%' ;
                        drop table yuubin; select * from yuubin where zipcode like '123%'" );

 このようにユーザが入力した文字列を元に,データベース検索を行う場合,;や'などに注意が必要で,入力チェックやエスケープを実施する必要があるのですが,別の方法としてデータベースのバインドメカニズムを利用したものが,最初に書いたBCなのです.
 何となく想像できると思いますが,?でSQL文を準備しておいて,executeの際に?に当る文字列を指定するというものです.このときの動きとして,$keyに書かれた文字列は,SQL文として解釈されることなく,純粋に文字列として扱われるため,;や'が含まれていても,先ほどのようなテーブルを消すような処理は行われません.
 
21. 検索結果の表示   ↑top

  検索結果を表示するための画面が必要ですが,これを作成してみます.前述のデータベース検索の結果は$sthで引き継がれています.この他,検索条件を画面表示するために,選択項目や入力時の文字列をいっしょに引き継いでいます(@).

【リスト 検索結果の画面表示 (search.cgi 5/5)】

sub result {
  my $item = shift;  ・・・@
  my $input = shift;
  my $sth = shift;

  if ($item eq "zipcode") {  
    print "郵便番号が <b>", $input, "</b> で始まるものを以下に示します.<br><br>";  ・・・A
  } elsif ($item eq "address") {
    print "住所に <b>", $input, "</b> を含むものを以下に示します.<br><br>";
  }

  if ($sth->rows == 0) {  ・・・B
    print "該当するデータはありません.";
  } else {
    print "検索件数:", $sth->rows, "<br>";

    print "<table>";  ・・・C
    print "<tr bgcolor=\"#a0a0ff\"><th>郵便番号</th><th>住所</th></tr>"; ・・・D
    my $bgcolor = "";

    while (my @row = $sth->fetchrow_array()) {  ・・・E
      if($bgcolor eq "#f0f0ff"){  ・・・F
        $bgcolor = "#d0d0ff";
      } else {
        $bgcolor = "#f0f0ff";
      }
      print "<tr bgcolor=\"$bgcolor\">"; ・・・G
      print "<td>", substr($row[0],0,3), "-", substr($row[0],3,4), "</td>"; ・・・H
      print "<td>", jcode($row[1])->sjis, "</td>"; ・・・I
      print "</tr>";
    }
    print "</table>";
  }
}

 検索結果として,まず,検索キーを表示し(A),続けて検索結果の数を表示するようにしています(B).その後,$sthからfetchrow_array()を使って1つずつ取り出して,無くなるまでwhileループを繰り返すようにしています(E).これを,TABで区切って表示してもいいのですが,ここでは少し見栄えがするように表形式で出力するようにしてみます.これには,<table></table>タグ(C)を使って,表の項目名を<th></th>(D)で,データ1行を<tr></tr>(G),データの各項目(ここでは郵便番号と住所)を<td></td>(HI)で囲むことで実現しています.細かいことですが,郵便番号を285-0845という形式に変更する処理を加えています(I).これに加えて表を見やすくするために,1行ごとに bgcolor で色を変えるように設定しています(F).好みの色に変更してみてください.

【検索結果画面イメージ】

 
22. ソース全体   ↑top

#!C:/perl/bin/perl.exe

use strict;
use CGI;
use Jcode;
use DBI;
use Apache::DBI;

print "Content-type: text/html\n\n";
print "<html><head><title>検索結果</title></head><body>";

(my $item, my $input) = input();
my $key = check($item, $input);
(my $dbh, my $sth) = db_search($item, $key);
result($item, $input, $sth);
$sth->finish(); $dbh->disconnect();

print "</body></html>";

### 入力情報の取得 ###
sub input {
  my $query = new CGI;
  my $item = $query->param('item');
  my $input;

  if ($item eq "zipcode") {
    $input = $query->param('zipcode');
  } elsif ($item eq "address") {
    $input = $query->param('address');
  }
  return $item, $input;
}

### 入力チェック ###
sub check {
  my $item = shift;
  my $input = shift;

  if ($item eq "zipcode") {
    $input =~ s/-//;
    if ($input =~ /^$/) {
      print "郵便番号が入力されていません.";
      Apache::exit;
    } elsif ($input !~ /^[0-9]{3,7}$/) {
      print "郵便番号の入力に誤りがあります.";
      Apache::exit;
    }

  } elsif ($item eq "address") {
    if ($input =~ /^$/) {
      print "住所が入力されていません.";
      Apache::exit;
    } elsif ($input =~ /[<>&"';]/) {
      print "住所の入力に誤りがあります.";
      Apache::exit;
    } else {
      $input = jcode($input)->z2h;
    }
  }
  return $input;
}

### データベース検索 ###
sub db_search {
  my $item = shift;
  my $key = shift;
  my $sth;
  my $dbh = DBI->connect(
    "dbi:Pg:host=localhost;dbname=Administrator","Administrator","")
        or die "データベースに接続できません.$DBI::errstr";

  if ($item eq "zipcode") {
    $sth = $dbh->prepare( "select * from yuubin where zipcode like ? order by zipcode" );
    $sth->execute("$key%");

  } elsif ($item eq "address") {
    $sth = $dbh->prepare( "select * from yuubin where address_kana like ? order by zipcode" );
    $sth->execute("%$key%");
  }
  return $dbh, $sth;
}

### 検索結果の画面表示 ###
sub result {
  my $item = shift;
  my $input = shift;
  my $sth = shift;

  if ($item eq "zipcode") {
    print "郵便番号が <b>", $input, "</b> で始まるものを以下に示します.<br><br>";
  } elsif ($item eq "address") {
    print "住所に <b>", $input, "</b> を含むものを以下に示します.<br><br>";
  }

  if ($sth->rows == 0) {
    print "該当するデータはありません.";
  } else {
    print "検索件数:", $sth->rows, "<br>";
    print "<table>";
    print "<tr bgcolor=\"#a0a0ff\"><th>郵便番号</th><th>住所</th></tr>";
    my $bgcolor = "";

    while (my @row = $sth->fetchrow_array()) {
      if($bgcolor eq "#f0f0ff"){
        $bgcolor = "#d0d0ff";
      } else {
        $bgcolor = "#f0f0ff";
      }
      print "<tr bgcolor=\"$bgcolor\">";
      print "<td>", substr($row[0],0,3), "-", substr($row[0],3,4), "</td>";
      print "<td>", jcode($row[1])->sjis, "</td>";
      print "</tr>";
    }
    print "</table>";
  }
}
 
23. 番外編(Javaからのアクセス)   ↑top

   PostgreSQL 7.2 (supports 7.1.x)  Java2(JDK1.2.x, JDK1.3.x, JDK1.4.x)   というものがあります。pgjdbc2.jarというファイルで、どこか適当なところにおいてください。コンパイルや実行時のオプションで指定してもいいですが、毎回めんどくさいのでシステムでCLASSPATHを張ります。システムのプロパティの詳細を選び、システム環境変数のCLASSPATHを選んで編集をクリックします。行の最後にでも C:\jdk1.3.1\pgjdbc2.jar をセミコロン(;)で区切って追加します。

リスト db_test.java

import java.util.*;
import java.sql.*;

class db_test {

  public static void main( String[] args ) throws SQLException {

   // JDBCドライバのローディング
    try {
      Class.forName("org.postgresql.Driver");
    } catch (ClassNotFoundException ex) {
      System.out.println("ドライバが見つかりません");
    }

    // DBに接続する
    Connection conn =
      DriverManager.getConnection("jdbc:postgresql://localhost/Administrator",
                                                          "Administrator", "");

    // ステートメントの作成
    Statement stmt = conn.createStatement();

    String sql = "select * from yuubin " +
                      "where zipcode like '9500%';";

    //
    ResultSet rset = stmt.executeQuery(sql);

    String zipcode;
    String address;

    while (rset.next ()) {  // 検索結果分
      zipcode = rset.getString(1); //
      address = rset.getString(2); //

      System.out.println("郵便番号:" + zipcode + "   住所:" + address);
    } // while
  }
}
 
 
23. 番外編(Tomcatからのアクセス)   ↑top

http://jakarta.apache.org/builds/jakarta-tomcat/release/v3.3.1/bin/jakarta-tomcat-3.3.1.zip
 
24. まとめ   ↑top

 後半は、主にPerlスクリプトで郵便番号および住所の一部を使った検索の例を用いて,基本となる要素をいくつか具体的に取り上げたつもりですが、なにぶんつたない説明で初心者には分かりにくく、経験者には物足りない内容になっていることと思いますがご容赦ください.

 今後,この環境をもとに,よりよいユーザインタフェースにめざすもよし.Linux上での動作など,環境のバリエーションを広げたり,性能向上のためのチューニングを行うもよし.今回ほとんど取り上げていないセキュリティへの配慮を深めるもよし.いずれも,不特定多数から利用を考えたときに実現していく必要のある項目ですが、初めての方はあまり焦らずに,動くことを確かめながら,着実に習得していかれることがよろしいかと存じます.

 とにかく、実際に触って楽しむのが一番.次の日曜日にでも、試してみてはいかがでしょうか.
 
 
.    ↑top


[HOME] / [Java] / [Perl5] / [Palm(GCC)] / [Palm(CodeWarrior)] / [EJB] / [本の紹介]

このページにご意見のある方は、egami@ee.e-mansion.com までお願い致します。