[PR]テレビ番組表
今夜の番組チェック

DelphiでCGI

0.デルファイ3.1プロフェッショナル版以上

新しくCGIアプリケーションに対応したクラスが装備されています。入出力もカプセル化されて便利になっています。基本的な使い方を示しますのでご参照下さい。もちろん、このクラスを使わなくても、今までどおりのCGIも作れます。その際は以下に進んで下さい。

1.はじめに 最近では、ウィンドウズでも様々なWWWサーバが使えるようになってきました。ネットスケープやマイクロソフトのサーバなど広く使われています。その他にも各社からサーバがでています。いずれもCGIがサポートされていると思います。

WWWサーバを動かしていると、いつも決まったページを表示するばかりでなく、データベースの検索をしたり、インタラクティブなページを使いたい(使わなければならない)といった状況が生まれてくることと思います。

しかしいざ、CGIプログラムを作ろうとすると「perl」や「シェル・スクリプト」(バッチファイル)を使わなければならないという壁に突き当たります。(これらを使うように指導している参考書が多いように見受けられます)。しかし、これらの言語では、簡単なことはできますが、使い慣れていないこともあって、データベース検索まで至るにはなかなか難しいことと思います。

最近では、デルファイやビジュアルベーシックでもCGIを作成することができるようになりました。これらでは簡単にデータベースを含むCGIが作れると思います。そこで、このページでは、Delphiを使ったCGIの作成体験をお話ししたいと思います。

2.WinCGI

wincgiはwindowsのiniファイルを介して、サーバとCGIプログラムが会話します。そのためDelphi1.0のような16ピットアプリでも動作します。しかしファイル読み書きの時間がとられるために、反応は遅くなります。さらに、個人的な体験ですが、ネットスケープサーバでは、同じソースコードを使っても、出力ファイルの共有違反のためDelphi2.0では動作しませんでした。また、INIファイルを読み込み解釈しなければ、出力すらできませんので、プログラムも煩雑になります。NETSCAPEのWWWに例が出ていたと思いますが、おすすめしません。

3.CGI

ウィンドウズのWEBサーバでも、UNIXと同等のCGI(標準入出力と環境変数を使用)が使えます。また、Delphi2.0からは「コンソールアプリケーション」を作成できるようになりました。このコンソールアプリケーションはウィンドウズの標準入出力を使いますので、簡単にCGIができます。

・メニューの「プロジェクト(P)」を選び、「オプション」のダイアログボックスを開きます。その中の「リンカ」のタブを開くと、左側の中程に「EXE/DLLオプション」というコーナーがあり、その中に「コンソールアプリケーションの作成」というチェックボックスがあります。はじめにこれをチェックしてからコンパイルをすれば、簡単にコンソールアプリケーションが作れます。もちろんCGIだけでなく、MS−DOSプロンプトで実行する様々なプログラムも作ることができます。

しかし本当は、コンソールアプリケーションである必要はありません。通常のフォームを使ったアプリケーション中でも「writeln」は標準出力にデータを書き出してくれますので、問題なくCGIを作ることができます

またコンソールアプリケーションの場合でもフォームやメッセージボックスを使うことができるようですので、たとえば、プログラムの途中に「showmessage」を利用して変数の確認をすることも可能です

ただ、CGIプログラムが終了しないと、WWWサーバもクライアントもお手上げとなってしまいますので、メッセージを出したまま止まっていたり、無限ループにはまりこんだりという事態にはならないように注意してください。

4.CGIの第一歩

コンソールアプリケーションには、フォームやユニットはいりません。プロジェクトソース(DPRファイル)だけで作れます。

・アブケーションの新規作成をするとフォームとユニットが一つずつできます。次にメニューの「プロジェクト」−「プロジェクトの削除」を選び、ユニット1を消します。その後、メニューの「表示」−「プロジェクトソース」を選ぶとDPRファイルだけのプログラムができます。これはDLLを作る時と同じです。

適当なファイル名をつけて一度「プロジェクトの保存」をおこないます。
DPRファイルができたらUSES節を含めて必要な形に書き換えます。
Program sample
      uses Windows;
 begin
      Writeln('Content-type:text/plain');
      Writeln('');
      {内容を示すこの2行は必須です}
      Writeln('こんにちは。Delphiで CGIです');
 end.
これでCGIができます。「コンソールアプリケーション」のチェックボックスを選んでからコンパイルします。後はできあがったEXEファイルを、WEBサーバのCGIのディリクトリに転送すれば出来上がりです。

http://***.***.***/scripts/sample.exe
または
http://***.***.***/cgi/sample.exe   (netscape serverのとき)

***.***.***の部分はホスト名または IPaddress です。スタンドアロンのパソコンの場合は「localhost」で良いと思います。

WEBサーバの設定にもよりますが、上記のような形でCGIが実行されると思います。エラーの時もエラーメッセージがクライアント側の画面で見えるだけですので、エラーをおそれることなく、いろいろと変更して、どんどん実行してください。(なお、関数を使ったときはUSES節にユニット名の追加が必要なことがあります。)

5.データの受け取り方法

ここまでのテクニックではWEBサーバからどのようなデータでも出力はできますが、アクセスしてくれた人からの入力を受け取ることはできません。入力を受け取るには2つの方法があります。フォームを使って送ってもらう方法(POSTメソッド)とURLに混ぜて送ってもらう方法(GETメソッド)です。

GETメソッドは

http://***.***.***/cgi/sample.exe?1234

といった形のURLから「1234」をデータとして受け取るものです。ISINDEXタグを使うと簡単に使用することができます。(WWWの本を参考にしてください)

POSTメソッドは広く使われている形です。URLは通常通りで、データだけが別に送られてきます。渡されるデータは英数字であれば問題がありませんが、漢字や半角カナではコードの解釈や変換が必要となります。漢字についてはUNIXにnkfというコード変換のソフトがあり、これはウィンドウズ用のDLLとして移植されています。

ただし、クライアントにUNIXのマシンがなく、かつフォームにシフトJISを用いたとき(英数字のみでは不可)は、返ってくるコードはすべてシフトJISになるようですので、その場合、コードの変換の必要はありません。

6.GETとPOSTの識別

この区別はWEBサーバが環境変数として渡してくれます。APIを使った以下のプログラムで読みとることができます。
*リクエストの種別を知る部分*
 var
     nsize,nbsize:integer;
     lpname,lpbuffer:pchar;
     REQUEST:string;
 
begin
      nsize:=255;
      lpname:='REQUEST_METHOD';{この文字はサーバの解説を参照してください}
      lpbuffer:=stralloc(nsize);
     nbsize:=getenvironmentvariable(lpname,lpbuffer,nsize);
      REQUEST:=strpas(lpbuffer);
      strdispose(lpbuffer);
     if REQUEST='POST' then
            post{POSTの時のコードを記述}
     else
            get;{GETの時のコードを記述}
 end;

7.POSTメソッドでの入力

さらにPOSTメソッドではフォームから何バイトのデータが送られてきたかを把握し、ちょうど、その分だけを読み込む必要があります。送られてきた以上にデータを読もうとするとエラーが発生します。まず送られてきたデータの量(バイト数)を求めるプログラムは以下のようになります。
*何バイトのデータがきているかを知る部分*
 var
     nsize,nbsize,contentlength:integer;
     lpname,lpbuffer:pchar;
     REQUEST:string;
 
begin
     nsize:=255;
     lpname:='CONTENT_LENGTH';{この文字はサーバの解説を参照してください}
     lpbuffer:=stralloc(nsize);
    nbsize:=getenvironmentvariable(lpname,lpbuffer,nsize);
     contentlength:=strtoint(strpas(lpbuffer));
     ............
つぎにデータを解釈しながら文字列を読み込みます。ただし漢字コード変換は行っていません。
*送られてきた文字の読み込み*
 var
     moji:integer;
     c:char;
     intwork1,intwork2:integer;
     CONTENT:string;{グローバル変数にしておくと便利かもしれません}
 begin
     moji:=1;
     while moji<=contentlength do{入力されたバイト数だけ}
     begin
          read(c);{一文字ずつ読み込む}
          inc(moji);
          if c='%' then
{「%B9」のように「%」のついた文字は16進法で送られてきたコードなので、これを1文字に戻すための操作}
{この部分についてはさらに洗練されたコードにできるはずですが、著者の体力不足からこのままにしておきます}
              begin
                  read(c);{1文字目}
                  inc(moji);
                  intwork1:=ord(c);
                  if intwork1<60 then
                         intwork1:=intwork1-48
                      else
                         intwork1:=intwork1-55;
                  read(c);{2文字目}
                  inc(moji);
                  intwork2:=ord(c);
                  if intwork2<60 then
                         intwork2:=intwork2-48
                      else
                         intwork2:=intwork2-55;
                  c:=chr(intwork1*16+intwork2);
               end;
         CONTENT:=concat(CONTENT,c);{1文字ずつ結果に加えて行く}
    end;
 ..........

8.GETメソッドでの入力

こちらはバイト数やコードはあまり考える必要はありません。パラメータとしてそのままデータが渡されます。したがって、通常はこちらを使った方がプログラムが簡単になると思います。

CONTENT:=paramstr(1);

とするだけでデータが読み込めます。もちろん、変数 paramcount でいくつのデータが渡されたかを知ることもできます。

9.VCLオブジェクトを使う

ここまでくると、データを受け取ったり、それに対応した結果を返すことができます。しかし、データベースを使うなどの少し複雑なことをしようとすると、VCLのオブジェクトを使いたくなるのも当然と思います。また、逆に、CGIでオブジェクトが使えるからこそ、デルファイでCGIを作る意義もあるというものでしょう。

早速オブジェクトを使ってみようとして

table1:=ttable.create(self);{selfが不可}

と書くと、コンパイル時にエラーになってしまいます。これはフォームが無いと、「self」が存在しないためです。しかし、その上位のオブジェクトとしてすべてのアプリケーションは「application」 というオブジェクトを持っています。これを使えば問題ありません。

table1:=ttable.create(application);

いちおう、非ビジュアル系のオブジェクトに限るという説もあるようですが、実際やってみるとなんでも大丈夫な様です。問題があるときにはオブジェクトを「フォーム」として作成する事もできます。

application.createform(ttable,table1);

この場合も親となるのは「application」オブジェクトです。もちろん、これらのオブジェクトを作る前には変数定義が必要です。

var

table1:ttable;

のように定義を加えておいてください。

10.データベースアプリをかく

いよいよ、お膳立てが整って、データベースのアプリケーションもかけることと思います。データベースの項目には数字や日付や文字が混在しています。これらをいちいち変換してHTMLにするのは面倒なことです。

そこで、TDBEdit を使います。このオブジェクトを使うとデータの型に関係なく、すべて文字にしてもらえるので楽です。データベースの検索と出力の例を示します。

もちろんフィールドの属性が明らかで、出力するだけでしたら
table1.fields[1].asString
とすれば、tableオブジェクトだけで足ります。
*データベースから1件だけ検索して、表として出力する部分*
 var
     CONTENT:string;{グローバル変数として、フォームの入力結果がこの「CONTENT」に入っているものとします}

procedure dbwrite;
 var
     Table1:TTable;
     Datasource1:TDatasource;
     DBEdit1:TDBEdit;
     DBEdit2:TDBEdit;
 begin
   {オブジェクトの作成}
     Table1:=ttable.create(application);
     Datasource1:=tdatasource.create(application);
     DBEdit1:=tdbedit.create(application);
     DBEdit2:=tdbedit.create(application);
   {プロパティの設定}
     Table1.databasename:='MYDATABASE';{BDEで alias を作っておくと簡単}
     Table1.tablename:='address.dbf';
     Table1.indexname:='index1';{すばやい検索をするにはインデックスの作成が必要}
     datasource1.dataset:=table1;
     dbedit1.datafield:='name';
     dbedit1.datasource:=datasource1;
     dbedit2.datafield:='address';
     dbedit2.datasource:=datasource1;
   {検索実行}
     table1.active:=true;
     table1.setkey;
     table1.findnearest([CONTENT]);
   {表示}
     writeln('<TABLE BORDER=2 CELLPADDING=3>');
     writeln('<TR><TH>氏名</TH><TH>住所</TH></TR>');
     writeln('<TR><TD>'+dbedit1.text+'</TD><TD>'+dbedit2.text+'</TD></TR>');
 end;
なお、この方法ではローカルのデータベースへのアクセスはうまくゆきますが、リモートのデータベースアクセスはうまく行かないという連絡もありました。リモートのデータベースとの接続には時間がかかるため、CGIが起動するたびに毎回接続するよりも、常時接続するようなプログラムを走らせておいて、CGIがサーバとそのプログラムとの間の中継のみを行うようにした方が効率的と思います。(ここまでくるとプログラムはかなり複雑になりますが・・・)

なお、ローカルデータベースでは毎回CGIで開いたり閉じたりしていても、ほとんど時間はかからず、別なプログラムにする必要はないと思います。(スワップしないようにメモリは十分用意する必要があります)

11.おわりに

そのようなことで、CGIは比較的簡単に書くことができます。

デルファイでOpenGLを使うには