oo4oをASPで使うための極意を習得しよう

ASP with Oracle

初音 玲 HATSUNE, Akira


Internet Information Server(IIS)の特徴のひとつが,Active Server Pages(ASP)の搭載だろう.そして,ASPは,VBSと呼ばれるVisual Basicライクな言語でプログラミングできることがその魅力を倍増させている.ただし,VBSのVisual Basic度数は,現在のVBAほどは高くない.まだ,VBAがExcelマクロと呼ばれていたころのように,似て非なる部分が多数存在し,また一見同じように見えていても,実際の動作が異なっている部分もある.もちろんこのような差異は,時間が解決してくれるだろう.Visual Basic 6.0のWebクラスのように,HTMLページをオブジェクトとして扱う仕組みは出始めている.その方向性が使いやすいかどうかはわからない.たぶん,使いづらい部分もあるだろうが,“Visual Basicさえ覚えれば,OfficeもWebもプラスαの知識でプログラミングできる”というマイクロソフトの戦略が変わらない限り,Visual Basic度数はバージョン番号と共に上がっていくだろう.だが,もうしばらくはVBS独特の世界観の中でプログラミングすることになるだろう.

C/Sではなく,Webアプリケーションにする意味は?

 C/SよりもWebアプリケーションが優れている点として,「配布工数の削減」が挙げられることがある.確かに,C/Sアプリケーションの1回目の配布では,COMコンポーネントやランタイムルーチンのようなレジストリに登録が必要なものを,単なるファイルコピーではなくインストールする必要がある.そしてそのためには,セットアップウィザードなどによりセットアップディスクを作成する必要がある(余談ではあるが,私はセットアップウィザードではなく,文化オリエント社のWISEを使っている).しかしそれ以降はCOMコンポーネントを修正しない限り,必要な作業はファイルコピーだけなので,それこそDOSのXCOPYコマンドでも十分対応できる.C/Sに比べてWebアプリケーションは配布する手間が必要ないなどというのは,別にWebアプリケーションの優位性ではないのだ.
 では,何が優位なのだろう.
 私は普段,Visual Basicで業務アプリケーションのプログラミングをしている.目下の悩みは,画面の見栄えなどビジネスロジックとは別の部分の変更依頼が多いことだ.もちろんVisual Basicを使っているので,そのような変更は比較的簡単にできるのだが,度重なるとイヤになるときがある.このイヤさは「修正したモジュールの配布が大変だ」などというカッコの良い理由ではない.単に面倒だからだ.客先に開発環境をインストールしたPCがあるときはよいが,小規模なお客様などでは,そのようなPCが確保できない事が多く,簡単な修正でも「お持ち帰り」になることも多い.スタンドアロンで使う業務アプリケーションを開発していたときは,このようなことは皆無であったが,C/S化されたシステムではときどき見受けられる.しかも,納品直後は開発環境がインストールされていたPCを久しぶりに使おうとしたら,「あっ,それ,調子悪いんで,OS再インストールしたんだ.ごめん,面倒なので開発環境は入れていないよ」などと言われることもある.Webアプリケーションならば,このような悩み事とも決別できる.なにせテキストエディタさえあれば,なんとかできるのだ.

IISではなくASP

 タイトルにもあるように,今回の記事の中心はIISではなくASPだ.なぜ,あえてASPをターゲットとして選んだのかといえば,IISはWindows NT 4.0 Serverを持っていないと使えないからだ.ASPならばWindows 95/98,Windows NT 4.0 Server/Workstationと,マイクロソフトのOSすべてで動作する.よってWindows 95でPersonal Web Serverを追加インストールしてASPを使うことも可能で,サンプルプログラムなども気軽に試してもらうことができる思う.
 今回の記事は,ASP 2.xを前提にする予定だった.ASP 2.xには目新しい機能もあり,また,パフォーマンスもASP 1.xに比べて優れている.導入にしてもWindows NT Option Pack(以下Option Pack)をセットアップすれば,起動しているOSに対応した新しいWebサーバーに更新される.たとえばWindows 95ならば,Personal Web Server 4.0だ(表1,図1).このOption Packは,Visual Studio 6.0にも付属しており,Enterprise版なら3枚目,Professional版なら2枚目に入っている.
 しかし,問題が生じてしまった.サンプルプログラムを,すべてIIS 4.0上で動作確認してから,久しぶりにWindows 95を立ち上げPWS 4.0を入れてサンプルを動作させたところ,正常に動作しないのだ.どうやら,Net8のクライアントモジュールが異常終了しているようだ(コラム1).原因は,PWS4.0を導入することでWinsock系のモジュールが置き換えられてしまうため,動作しないモジュールがでてくるのだ.もしかしたら,Windows 98の通信を伴うソフトウエアで動作しないものは,同じことが原因なのかもしれない.
 そこで,サンプルとしてASP 1.x版も作成した.これで,Windows 95でも気軽に試すことができると思う.それに,Personal Web Server 4.0では,IE4.01のインストールが必須なので,IE4.01を入れたくないときもASP 1.xなら動作させることができる.もちろん,開発言語として,Visual Basic 4.0またはVisual C++4.0,Office95などを使っているPCに,IE 4.xをインストールするのは論外だ.

図1:PWS 4.0
図1:PWS 4.0 (500*351)

表1:OSとASPの関係
OS Webサーバー ASP
Win 95 Personal Web Server 1.0 1.x
Win 95 + NT Option Pack (for Win9x) Personal Web Server 4.0 2.x
Win 98 Personal Web Server 4.0 2.x
NT4.0 Workstation(Service Pack 3) Peer Web Server 3.0 1.x
NT4.0 Workstation(Option Pack 1) Personal Web Server 4.0 2.x
NT4.0 Server Internet Information Server 2.0 1.x
NT4.0 Server(Service Pack 3) Internet Information Server 3.0 1.x
NT4.0 Server(Option Pack 1) Internet Information Server 4.0 2.x


コラム1:NT Option PackとOracle8の相性について
 Windows95にVisual Studio 6.0のOption Packを導入すると,Oracle8のクライアントモジュールであるNet8 Clientなどが使用不能になる.これは,Option Packで更新されるJavaVMとの相性が悪いという問題だけではないようだ.現に,TNSPING80などもページ違反で落ちてしまう.
 しかも「アプリケーションの追加と削除」でPersonal Web Serverを削除しても改善されない.この状態でOracle8クライアントモジュールのアンインストールとインストールを行なっても駄目だ.
 このとき「OS再インストール」の文字が頭の中を駆け巡ったのは言うまでもない.
 しかし,復旧方法は意外なところにあった.Option Packは導入前の環境を残しておいてくれるらしく,Windowsディレクトリ配下にWS2BAKUPディレクトリが作成されていて,DOSモードで立ち上げてから,このディレクトリ中のWS2BAKUP.BATを実行すればよい.これでOracle8クライアントを動かすことができる.このBATファイルの内容を参照してみると,Option PackではWinsockなどのモジュールが入れ替えられるようである.
 試しに,再度Option PackをインストールしてからWS2BAKUP.BATを起動してみたところ,PWS4.0自体が上がらなくなった.
 どうやら,PWS4.0とOracle8クライアントの同居は不可能であるようだ.現状では,PWS4.0を使うならOracle7クライアントを使うしかない.Oracle8にも,SQL*Net2.3Clientからだったら接続可能だ.また,IIS4.0をインストール後にOracle8クライアントをインストールしたときも正常に動作することを確認しているので,PWS4.0独自の問題のようだ.
よって今回の動作環境は,

Win 95 + PWS1.0 + oo4o2.2.3 + SQL*NetClient 2.3.4 (確認済)
Win 95 + PWS1.0 + oo4o2.2.3 + Net8 Client 2.3.4 (確認済)
Win 95 + PWS4.0 + oo4o2.2.3 + SQL*NetClient2.3.x
Win 98 + PWS4.0 + oo4o2.2.3 + SQL*NetClient2.3.x
WinNT4.0WS + PWS4.0 + oo4o2.2.3 + SQL*NetClient2.3.x
WinNT4.0SV + IIS4.0 + oo4o2.2.3 + SQL*NetClient2.3.4 (確認済)
WinNT4.0SV + IIS4.0 + oo4o2.2.3 + Net8Client8.0.4 (確認済)

のいずれかに限定したい.なお,確認済み以外の組み合わせは,正しく動作しない可能性もあることをご了承いただきたい.なお,PWS1.0自体には,ASPは含まれていないので,http://www.asia.microsoft.com/japan/products/iis/download/asp/download.htmからASP1.0をダウンロードする必要がある.

ASPとは

 ASPの実体は,ASP.DLLというActiveX DLLファイルだ.よって,IISやPWSからはインプロセスCOMサーバーとして起動され,ASPのスクリプト実行中に異常終了すると,IISやPWS自体まで終了する.安全性よりもパフォーマンスを優先させた構造だ.そのため心あるWebアプリケーション開発業者は,たとえユーザー要望でIISの採用がきまったとしても,ASPではなくCGIなどの手段を使ってWebアプリケーションを構築していた.ちなみにCGIは標準EXEファイルなので,CGI中で異常終了してもIISまたはPWSまで終了することはない.
 最近はこの状況も変化してきた.IIS 4.0からはASPをアウトプロセスCOMサーバーとして起動(図2)できるオプションが用意された.これで共倒れによるWebサーバーの停止は防止できる.もちろんパフォーマンスは犠牲になる.マイクロソフトのWebサイト(http://www.microsoft.com/japan/products/iis/overview/benchmarks.htm)からの抜粋ではあるが,インプロセスCOMサーバーとしてASPを使ったときと,アウトプロセスCOMサーバーとしてASPを使ったときのパフォーマンスの差を図3にまとめた.

図2:新たな起動オプション
図2:新たな起動オプション (400*175)

図3:パフォーマンスの差
図3:パフォーマンスの差 (400*184)

ASPの極意は,サーバーサイドスクリプト利用にあり

 ASPの特徴のひとつとして,スクリプトエンジンが外付けになっている点が上げられる.標準では,VB ScriptとJ Scriptのスクリプトエンジンが添付されている.やる気になれば,PL/SQL Scriptエンジンを実装して,あたかもOracle Web Application Server上で,PL/SQLカートリッジを使って動作しているようなスクリプトを書くことも可能かもしれない.
 ASPを使って,ブラウザ側で動作するスクリプトを記述することもできるが,私はどうもその手のものが好きではない.やはり,サーバーサイドスクリプトが好きだ.多分に好みの問題もあるかもしれないが,サーバーサイドスクリプトで複雑なロジックを隠蔽して,結果を基本的なHTMLのみで記載することで,ネットワークトラフィックやセキュリティを確保できると考えている.

ASPの極意は,COMコンポーネント利用にあり

 IISやPWSにとってASPがCOMコンポーネントであるように,ASPを使う上でCOMはとても重要なものだ.たとえばVBからCOMコンポーネント(ActiveX DLL,ActiveX EXE)を使うことができるように,ASPからもCOMコンポーネントを使うことができる.これは,Webの表現力をアップする近道があることを意味する.たとえばgifやjpegに結果を書き出すなり,特定のファイル名でhtmlファイルを書き出すなりするCOMコンポーネントを利用すればよい.これは,市販のCOMコンポーネントを利用しても良いだろうし,Visual Basicで作成してもよい.COMコンポーネントをVisual Basicで作成する方法は,本誌98年9月号の特集記事などを参照していただくとして,今回はASPからCOMコンポーネントを使う方法に注目してゆきたいと思う.

ASPの極意は,RDBMSにあり

 ASPは,静的なHTMLファイルではなく,動的にHTMLファイルを生成するときに威力を発揮する.そのためには,その元ネタをどこかに格納しておく必要がある.たとえば,Cookieによりそれぞれのクライアントに格納しておくのも手だろう.しかし,セキュリティの問題や1台を複数人で共通利用していたり,逆にひとりで複数台のマシンを使っているときに不便だったりする.そこで,RDBMSの出番である.RDBMSにさまざまなデータを用意しておいたり,個々のユーザーの設定情報を格納しておいたりすれば,それこそ会社でも家でもモバイルでも同じ設定で使うことができる.さらにIISのユーザー認証は,NTドメインなどのWindowsネットワークの認証に依存しているが,RDBMSを併用することで,RDBMSがもっている認証方法の利用や,ユーザーIDとパスワードをテーブルに保持して,それを使った柔軟性のある独自の認証方法を採用することもできる.

ASP with oo4o

 ASPからRDBMSに接続する場合,ADO1.5を使うのが一般的だろう.またVisual Basic 6.0でCOMコンポーネントを作成してADO2.0を使う方法もある.特にSQL Serverに対して,ADO1.5を使ってODBC Provider経由で接続する場合は問題も少ないと思う.しかしVisual Basicユーザにお勧めなのは,ASPからもRDO2.0で接続する方法だ.またRDBMSとして,SQL Serveと双璧をなすOracleに接続するときは,Oracle Objects for OLE(以下oo4o)を使うのがよいだろう.oo4oもV2.2以降では,マルチスレッドに対応しているので,ASP 2.xで効率よく動作させることが可能だ.
 ADOやRDOにしろ,oo4oにしろ,ASPで使うにはサーバーサイドでCreateObjectすることになる.今回は,oo4oに限定して話しを進めてゆくが,SQL Serverを利用するときには,oo4oの代わりにRDOをCreateObjectして,メソッドなどをRDOのメソッドに書き換えれば動作するはずだ.

ASP with oo4o〜基本的な接続方法〜

 Oracleとの接続を確認するために,テーブルの中身を表示するだけのサンプル1を作成した(リスト1:TestDisp.asp).このサンプルを動作させるためには,サンプルを格納した物理ディレクトリのどこかに,エイリアス(仮想ディレクトリ)を定義する(図4).今回は,VBMというエイリアスを定義し,このエイリアスにスクリプトの実行を許可する(図5).これで,ASP側の設定は完了だ.
 次にOracle側を設定する.
 手順としては,

  1. DOSプロンプトからping <RDBMSサーバー名>を入力して,RDBMSサーバーとTCP/IPで接続できるかを確認
  2. OracleのSQL*Net Easy Configもしくは,Net8 Easy Configを起動して,上記RDBMSサーバーを指定して,データベース別名を設定する.今回のサンプルでは,kawasakiというバイクメーカーの名前を採用した

である.
 Oracle側の設定も完了したら,ASPが動作しているPC上で,ブラウザを使って,


http://localhost/VBM/1.x/TestDisp.asp


 とURLを指定すると図6のような画面が表示されるはずだ.ASP 2.xを利用している時は,URL中の「1.x」を「2.x」に変更して欲しい.
 サンプル1では,RDBMSの存在を意識せず,動的に生成されるページを気軽に閲覧することができる.しかも,サーバーサイドスクリプトなので,RDBMSと接続するための情報がブラウザに送られることもないため,ページのソースを表示されても接続情報が漏れる心配もない.もし漏れたとしても,ブラウザとRDBMSサーバー間にファイアウォールなどが設置されていれば,情報の漏洩を十分防ぐことが可能だ.このように,誰でも閲覧できるページならば,サンプル1をベースにしてWebアプリケーションを構築することができる.
 今回は,サーバーサイドスクリプトとしてVBScriptを使っているので,Visual Basicのプログラムとほとんど同じ構文だ.

図4:仮想ディレクトリを定義する
図4:仮想ディレクトリを定義する (250*100)

図5:スクリプトの実行を許可する
図5:スクリプトの実行を許可する (477*159)

図6:IEに表示させたところ
図6:IEに表示させたところ (501*460)

リスト1:TestDisp.asp(ASP1.x用)抜粋
<HTML>
<HEAD>
<TITLE>ASP 1.x Test Display</TITLE>
</HEAD>
<BODY>
<H1>ASP 1.x Test Display</H1>
<% Call Echo %>
</BODY>
</HTML>

<SCRIPT LANGUAGE=VBScript RUNAT=Server>
Private Sub Echo
' Active Server Pages 1.xもしくはOracle Objects for OLE V2.1以前では
Set oraSess = Server.CreateObject("OracleInProcServer.XOraSession")	………(1)
' Active Server Pages 2.xかつOracle Objects for OLE V2.2以降では
' Set oraSess = CreateObject("OracleInProcServer.XOraSession")

Set oraDb = oraSess.DbOpenDatabase("kawasaki","scott/tiger",clng(3))	………(2)
Set oraDs = oraDb.DbCreateDynaset("select * from emp",clng(0))	…………………(3)

Response.Write "<table border=1>"	…………………………………………………(4)

' タイトル
Response.Write "<tr>"
For iintLoop = 0 to oraDs.Fields.Count - 1
 Response.Write "<th>" & oraDs(iintLoop ).Name & "</th>"	…………………(5)
Next
Response.Write "</tr>"

' レコードデータ
Do While Not oraDs.EOF
 Response.Write "<tr>"
 for i=0 to oraDs.Fields.count - 1
  Response.Write "<td>" & oraDs(i).Value & "</td>"	……………………………(6)
 Next
 Response.Write "</tr>"
 oraDs.DbMoveNext	………………………………………………………………………(7)
Loop
Response.Write "</table>"

Set oraDs=Nothing	………………………………………………………………………(8)
Set oraDb=Nothing
Set oraSess=Nothing
End Sub
</SCRIPT>

サンプルの動作について
(1)oo4oをCreateObjectして使用可能にする
(2)DbOpenDatabaseメソッドにより,Oracleと接続.もちろん,OracleサンプルユーザーScottのパスワードがTigerであることが前提
(3)DbCreateDynasetメソッドにより,Oracleのサンプル表empを読み込む
(4)結果をHTML表にするため,<TABLE>タグを生成.スクリプト中にタグを埋め込むときは,ASPのResponseオブジェクトのWriteメソッドを使う
(5)フィールド名を<TH>タグで囲んで生成
(6)フィールド値を<TD>タグで囲んで生成
(7)DbMoveNextメソッドにより,次レコードに移動
(8)使った外部オブジェクトは明示的に破棄

 ASP 1.xとASP 2.xの違いについて

 ASP 2.xとoo4o V2.2以降がマルチスレッド対応になったことにより,Serverオブジェクトの扱いが変更になった.そのため,ASP 2.x以降 + oo4o V2.2以降の組み合わせでは,CreateObject時に「Server.」を省略する.省略しなかったときは,「外部オブジェクトでトラップできるエラーが発生した.スクリプトの実行を続行できない.」というエラーが表示されるの注意が必要だ.この1行以外は,ASP 1.xとASP 2.xでのコーディング上の違いはない.

ASP with oo4o〜ユーザ認証をRDBMSに任せるには〜

 誰でもが閲覧できるサンプル1のパターンではなく,限定された人にのみを閲覧可能にしたいときはどうしたらよいだろう.IISのユーザ認証を利用する手もあるが,OSユーザとして登録する必要があり,OSを不正利用される危険性を含んでいる.そこでお薦めなのは,RDBMSのユーザ認証を使う方法だ.これは,最悪そのRDBMS以外の安全を確保しようとする考えがベースになっているので,RDBMS側でOS統合の認証になっているときは意味をなさない.
 RDBMSのユーザ認証を使う方法は,ごく普通にC/SでRDBMSにログオンするときと同様だ.ログオン画面とログオンに成功したときに表示される画面を用意すればよい.サンプル2ではIndex.htmがログオン画面になり,そこから起動されるOraLogon.asp(リスト2)がログオン結果を表示するスクリプトだ.
 ブラウザで,

http://localhost/VBM/1.x/OraLogon/Index.htm

とURLを入力すると図7のログオン画面が表示される.ここで,登録済みのユーザIDとパスワードとデータベース別名を入力すれば,図6の画面が表示される.この画面では,<FORM>タグを使ってブラウザ上での入力を実現している.このタグのACTIONオプションにaspファイルのファイル名を記述しておくと,submitタイプのボタンを左クリックすることで,aspファイルを呼び出すことができる.

図7:ログオン画面
図7:ログオン画面 (501*460)

リスト2:Oracleユーザで認証する(ASP1.x用)
<%@ LANGUAGE="VBSCRIPT" %>
<HTML>
<HEAD>
  <TITLE>Oracle Logon for ASP 1.x</TITLE>
</HEAD>
<%	……………………………………………………………………………………………(1)
  strUser = Trim(Request("USER_ID")) & "/" & Trim(Request("PASSWORD"))	………(2)
  strHost = Trim(Request("HOST"))

  Set oraSess = Server.CreateObject("OracleInProcServer.XOraSession")	………(3)
  Set oraDb = oraSess.DbOpenDatabase(strHost,strUser, Clng(0))	…………………(4)
  Set oraDs = oraDb.DbCreateDynaset("select * from emp", Clng(12))
%>	……………………………………………………………………………………………(5)
<BODY BGCOLOR="#FFFFFF">	……………………………………………………………(6)
<table border=1>

<tr>
<%
For iintLoop = 0 to oraDs.Fields.Count - 1
  Response.Write "<th>" & oraDs(iintLoop ).Name & "</th>"
Next
%>
</tr>

<%
Do While Not oraDs.EOF
  Response.Write "<tr>"
  for i=0 to oraDs.Fields.count - 1
    Response.Write "<td>" & oraDs(i) & "</td>"
  Next
  Response.Write "</tr>"
  oraDs.DbMoveNext
Loop
%>
</table>
</BODY>
<%
  Set oraDs = Nothing
  Set oraDb = Nothing
  Set oraSess = Nothing
%>
<% Session.Abandon %>
</HTML>

サンプルの動作について
(1)VB Scriptの開始を表すタグ
(2)<FORM>タグで指定された値をRequestメソッドで取得
(3)CreateObjectメソッドでoo4oと接続(ASP 1.xと2.xの違いはこの行のみ)
(4)指定された値で,Oracleと接続
(5)VB Scriptの終了を表すタグ
(6)VB Scriptの終了を表すタグのあとなので,普通にHTMLを記述可能.このようにaspファイル中には,HTMLとスクリプトの両方が混在可能だ

 なお,ユーザID,パスワードが正しくないときは,図8のようなエラーがブラウザに表示される.Visual InterDevなどを導入していたり,IIS 4.0上でスクリプとデバッガが有効になっていたときは,エラー表示前に,それらのデバッグツールが立ち上がることになる.
 Visual InterDevでのデバッグ方法は,Visual Basic IDE上でのデバッグ方法と同様だ.ステップ実行も可能だし,変数の値の参照および変更もできる.ただ,メニューに割り当てられたショートカットキーなどが異なるので,少々使い勝手に難があったことを報告しておく.

図8:エラー時の画面
図8:エラー時の画面 (475*286)

ASP with oo4o〜ユーザ認証の自由度を上げるには〜

 サンプル2では,OSのユーザ認証の代わりにRDBMSのユーザ認証を利用した.この両者は,認証するシステムが違うだけで,既存の認証系を使うことに変わりがない.手軽な半面,ユーザIDおよびパスワードの自由度が低かったり,ユーザ情報により更に細かくページを変化させたいときには向かない.そこで考えられるのが,認証用のテーブルを作成して,そのテーブルのレコードの有無で認証正否を判定する方法だ(リスト3:Select.asp).URLは,

http://localhost/VBM/1.x/PasswdCk/select.htm

だ.これで,図6と同様のログオン画面が表示されるはずだ.

リスト3:ユーザ認証テーブルを直接参照するサンプル(select.asp抜粋)
<%@ LANGUAGE="VBSCRIPT" %>
<HTML>
<HEAD>
  <TITLE>Oracle Logon for ASP 1.x</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF">
<%
  strHost = Trim(Request("HOST"))	…………………………………………(1)

  Set oraSess = Server.CreateObject("OracleInProcServer.XOraSession")
  Set oraDb = oraSess.DbOpenDatabase(strHost,"scott/tiger", Clng(0))
  strSQL = "SELECT ID FROM VBM.Password " & _
           "WHERE USERID='" & Trim(Request("USER_ID")) & "'" & _
           "  AND PASSWD='" & Trim(Request("PASSWORD")) & "'"
  Set oraDs = oraDb.DbCreateDynaset(strSQL, Clng(12))	……………………(2)
  If Not oraDs.Eof Then	………………………………………………………………(3)
  ' ログオン成功

サンプルの動作について
(1)データベース別名は,<FORM>で指定された値を使用
(2)ユーザIDとパスワードをWHERE句に指定したSQL文を作成して,DbCreateDynasetメソッドを発行
(3)レコードが存在したらログオン成功

 サンプル実行前に,Password.sqlを適切なユーザで投入しておく必要がある.このサンプルでは,実際のテーブル所有者として,VBMユーザを生成して,scottユーザには,select権限のみ与えることにより,scottユーザからのデータの改ざんを防止している.その権限を与えるのが,Select.sqlだ.

ASP with oo4o〜ユーザ認証の安全度を上げるには〜

 サンプル3では,select権限のみをscottユーザに与えることで,ユーザ情報の改ざんを防止した.しかし,select権限は存在するので,他のweb利用者の情報もscottユーザでみることができる.これでは,万が一,SQL*Plusまたは独自のプログラム,または,不正に登録されたページから,Oracleに接続されてしまったらまずい事になる.そこで,登録情報をscottユーザにも隠蔽しつつ,認証を行なうようにしたサンプルがリスト4(func.asp)だ.URLは,

http://localhost/VBM/1.x/PasswdCk/func.htm

となる.

リスト4:ストアドファンクションでユーザ認証を行なう(func.asp抜粋)
<%@ LANGUAGE="VBSCRIPT" %>
<HTML>
<HEAD>
  <TITLE>Oracle Logon for ASP 1.x</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF">
<%
  strHost = Trim(Request("HOST"))

  Set oraSess = Server.CreateObject("OracleInProcServer.XOraSession")
  Set oraDb = oraSess.DbOpenDatabase(strHost,"scott/tiger", Clng(0))
  oraDb.Parameters.ADD("ID"),0,Cint(3)	………………………………………………(1)
  oraDb.Parameters.ADD("USER_ID"),"#",Cint(1)	……………………………………(2)
  oraDb.Parameters.ADD("PASSWD"),"#",Cint(1)	……………………………………(3)
  oraDb.Parameters("USER_ID").Value = Trim(Request("USER_ID"))	………………(4)
  oraDb.Parameters("PASSWD").Value = Trim(Request("PASSWORD"))	………………(5)
  strSQL = "BEGIN :ID := VBM.Passwd(:USER_ID,:PASSWD); END;"	………………(6)
  oraDb.DbExecuteSQL strSQL	…………………………………………………………(7)
  If oraDb.Parameters("ID").Value > 0 Then	……………………………………(8)
  ' ログオン成功
%>

サンプルの動作について
(1)バインド`変数「ID」を入出力型で宣言
(2)バインド変数「USER_ID」を入力型で宣言
(3)バインド変数「PASSWD」を入力型で宣言
(4)バインド変数「USER_ID」にログオン画面の入力値を設定
(5)バインド変数「PASSWD」にログオン画面の入力値を設定
(6)認証用ストアドファンクションを呼び出すSQL文を生成
(7)(6)で生成したSQL文を実行
(8)認証用ストアドファンクションの戻り値が正の値ならば,ログオン成功

 サンプル3で投入していなければ,サンプル投入前に,Password.sqlを適切なユーザで投入しておく必要がある.また,認証用ストアドファンクションを登録するために,func.sqlをVBMユーザで投入する必要もある.このSQLスクリプト中では,scottユーザにストアドファンクションの実行権限を与えている(リスト5:func.sql).

リスト5:認証ストアドファンクション(func.sql抜粋)
/* User/Passwdチェック用ストアドファンクション       */
CREATE OR REPLACE
FUNCTION     PASSWD(
   rstrUSERID IN VARCHAR2,
   rstrPASSWD IN VARCHAR2) RETURN INTEGER IS
CURSOR curPasswd IS
  SELECT ID
    FROM Password
  WHERE USERID=rstrUSERID
    AND PASSWD=rstrPASSWD;
  intRet INTEGER;
BEGIN
  intRet := -1;	……………………………………………(1)
  FOR recPasswd IN curPasswd	………………………(2)
  LOOP
    intRet := recPasswd.ID;	………………………(3)
  END LOOP;
  RETURN intRet;	…………………………………(4)
END PASSWD;

認証ストアドファンクションについて
 func.sqlの処理内容は,以下の通り.
(1)戻り値の初期値を-1に設定
(2)カーソル「curPasswd」に対応したSELECT文に一致するレコードをカーソルFOR文ですべて取得.今回は,ユーザIDとパスワードを組み合わせてWHERE句を指定しているので,一致すれば取得されるレコードは1件.
(3)レコードのIDの値を戻り値に設定
(4)戻り値を指定して実行終了

 ストアドファンクションを始め,ストアドプロシージャおよびストアドパッケージは,その所有者が保持している権限で動作する.テーブル所有者でストアドファンクションを作っておけば,ファンクションが有している機能のみを適用して,情報を隠蔽することができる.たとえばscottユーザは,ストアドファンクションを通して認証用のテーブルを参照することはできるが,Func.sqlの中でselect権限を削除しているので,テーブルを直接指定しても参照することはできない(図9).これは,一種のオブジェクト化のようなものだ.

図9:テーブルを直接指定した場合
図9:テーブルを直接指定した場合 (501*460)

ASP 2.xの新仕様,global.asaを使う

 エイリアスで指定した物理ディレクトリにglobal.asaを作成しておくと,そのエイリアスを含んだURLを指定されたリクエストがIIS 4.0やPWS 4.0を起動して最初に到着したときだけ,その内容が評価される.マルチスレッド対応のoo4o V2.2以降では,このglobal.asaにCreateObjectを記載することで,ブラウザからのリクエストごとにoo4oと接続する時間を省くことができる(リスト6:global.asa).
 リスト6の(1)にあるように,RUNATでサーバーサイドを指定し,SCOPEでApplicationを指定する.ここで言うApplicationとは,エイリアス以降のディレクトリのASPファイル全体のことだ.IDには,いわゆるグローバル変数名,PROGIDにCreateObjectに指定した値を記述する.

リスト6:リクエストごとのoo4oとの接続を省略
<OBJECT RUNAT=Server SCOPE=Application ID=pOraSess
PROGID="OracleInProcServer.XOraSession">	…………………(1)
</OBJECT>
<SCRIPT LANGUAGE=VBScript RUNAT=Server>

Sub Application_OnStart
End Sub

Sub Application_OnEnd
End Sub

</SCRIPT>

結局,開発環境は必要だ

 Webアプリケーションは,「テキストエディタさえあれば客先でも簡単に修正できるのが利点だ」と記述した.しかし,ASPを本格的に利用したりパフォーマンスを考慮すると,COMコンポーネントを自作することになる.COMコンポーネントを自作するということは,ステップ実行してデバッグをしたり,WebサーバーにCOMコンポーネントを再インストールしたりすると言うことだ.つまり,結局は開発環境が必要になるということだ.
 このとき注意点として,運用中のWebサーバーを開発環境としないことだ.確かに運用中のWebサーバーを開発環境とすれば,実環境そのものでデバッグできるし,COMコンポーネントの配布も必要ない.COMコンポーネントを適切なディレクトリ配下にコンパイルするだけで非常に楽だ.しかし,デバッグ中にVBのIDEが無反応になってしまったり,メモリリークが発生したりして,サーバーをリブートしなければならない事態が発生することもある.だから,絶対,運用中のWebサーバーに開発環境を構築してはならない.「こんなものは常識だ」と思っていたのだが,最近はそうでもないらしい.スタンドアロン開発またはクライアント側開発しか経験したことのない開発者だと,サーバーサイドのアプリケーションは,サーバーに開発環境を用意してしまうようだ.
 では,Webアプリケーションの開発環境は,どうしたらよいのだろう.結論から言えば,Windows 95/98のノートパソコンにPersonal Web Serverをインストールして,デバッグするのがよいだろう.
 もうひとつの解決策は,自作COMコンポーネントにバグを混在させないことだ.当然と言えば当然なのだが,現実問題として,不可能と思われるかもしれない.複雑なビジネスロジックであれば,仕様変更およびバグの混在は避けられないものだ.そして,複雑なビジネスロジックをCOMコンポーネント化することで,パフォーマンスが向上することも事実だ.それにも,関わらずCOMコンポーネントにバグを混在させないことが現実的な解決策となりうるのだ.なぜなら,RDBMSと連携したとき,COMコンポーネント以外にもビジネスロジックの格納を行なう場所がある.複雑な部分さえなければ,仕様変更またはバグ混入の対象となる度合いも減少する.VBのIDEの出番も減るわけだ.RDBMSを利用しているときは,ストアドプロシージャを使ってRDBMS自身にビジネスロジックを実装する.実際,この方法の方が,WebサーバーとRDBMSサーバー間のネットワークトラフィックが減少するため,単にCOMコンポーネント化したときよりもパフォーマンスが向上する.
 しかし問題もある.開発用のRDBMSサーバーの確保,デバッグ方法の問題などだ.開発用RDBMSの確保については,本当はクライアント側またはWebサーバー(といってもRDBMSサーバーからみればクライアントでしかない)側にロジックを記述しているときも必要なことだ.しかし,少しの変更ならば開発用RDBMSを確保する必要性を感じないのは,Visual BasicまたはVisual InterDevなどにより,ロジックのステップ実行による確認が実現できているからだろう.つまり,デバッグ方法の問題に帰着する.SQL Serverならば,Visual Basic 5.0以上を購入することで,T-SQLデバッガが入手できる.Oracleについても,Developer/2000 R2.1以上を購入することで,Procedure Builderが入手できる.まあ,どちらにしても,それらをインストールした開発環境が必須ということには変わりがない.
 また,話しが横道に外れてしまうが,Developer/2000については,価格をVisual Basic 6.0 Enterprise Edition並に低価格化して欲しいと思っている.それが難しいのならば,Developer/2000 Standard Editionとして,Form,Query Builder,Procedure Builderのみのセットでもよい.Procedure Builder単体をAccess並の価格で発売するのでもよい.ぜひ,オラクルに一考して貰いたい.

最後に

 ASPを中心に,日ごろ感じていたことを書き連ねさせて貰ったが,他のライターさんのようにかっこよくて,為になる内容なのか不安でもある.かっこ悪くても,せめて少しでも記事の内容が役にたつように,これからも執筆してゆきたいと思う.機会があれば,Microsoft Transact Server(MTS)のコンポーネントをVisual Basicで作成して,IISから利用する方法なども執筆してみたいと考えている.

[動作確認環境]
Win95(Ver.4.00.950B)
 IE4.0(Ver.4.72.3110.8)
 oo4o(ver2.2.3.2.0)
NET8(Ver8.0.4.0.0)
 Visual InterDev 6.0
 Personal Web Server 1.0
NT4.0 Server(OP1)
 Internet Information Server 4.0
NT4.0 Server(SP3)
 Oracle 8 Enterprise Edition R8.0.4.0


コラム2:NT1台には,サービスひとつ
 コラム1に記載したことを考えると「NT Server 1台には,1サービス」というのもあながち無視できないと思う.今回はクライアントモジュールだけだったので,ロストするデータも,たかだかtnsnames.oraなどの接続先情報くらいだ.しかし,障害がRDBMSの入っているマシンで発生して,RDBMSのバックアップツールすらも動作しない状況になったらたまったものではない.やはりNT Server1台には,RDBMSだったらRDBMSだけ,IISだけだったらIISだけというのが安定稼動の王道だ.テスト的に同一サーバーに導入して正常に動作したとしても,運用中のマシンをそのような形態にすべきではない.
 たとえば,IISの機能アップやセキュリティホールを塞ぐような追加ソフトを導入した途端に,それまで元気に動いていたRDBMSが二度と立ち上がらなくなったら,これは大変なことだ.普通こういった作業を行なうとき,状況が許せば,1日くらいは確保できるかもしれないが,「パッチあてるので,30分ほど停止します」などと事前告知をして短時間で済ませることも多いだろう.そのような限られた時間で,元どおりRDBMSを稼動させるのは不可能かもしれない.はっきり言って,責任問題にも発展しかねない.
 さて,そういったことを踏まえると,Microsoft Small Buinness Serverは導入すべきではない.SBSはライセンス上,すべての製品を1台のマシンに導入しなければならないのだ.もっとも,わざわざSQL Serverに容量制限を施した特別仕様のものが使われているSBSをまともな業者なら選択することはないだろう.利用部門からの要望があるので,各社SBS対応のメニューはあるのは世の理であるし,そのようなメニューが存在すること自体を非難することはできない.でも,利用部門がそれを欲したときに,「これ,あまりお勧めできませんよ」と言ってくれる,良くないものは取り扱ってても売らない昔気質のバイク屋のおやじのような営業が増えることを期待する.

コラム3:ASPからVBを利用する
 このコラムは締切を過ぎてから執筆している.かといって締切に間に合わなかったわけではない.きちんと入稿したのだが,何か書き忘れている気がしていた.Oracle Objects for OLEもActiveX DLLなのだから,Visual Basicで作ったを呼び出すのも同じようなものだと考えていた.しかし,入稿後にちょっと時間があったので,Visual Basicで作ったActiveX DLLをASPから使おうとしたのだが,これが一筋縄ではゆかなかった.そこで急遽,追加原稿をこうして執筆しているわけだ.
 ASPから使うActiveX DLLを作成するとき面倒なことがある.それはどうやってデバッグするかだ.Visual Basic内ならば,ActiveX DLLとそれを呼び出す標準EXEを同一のプロジェクトグループに入れることで,標準EXEとActiveX DLLを行き来しながらデバッグできる.しかしASPではそのようなことができない.そこで,先ほどの標準EXEとActiveX DLLを同一プロジェクトグループに入れて,Visual Basic上で徹底的にデバッグする手法をお勧めする.
 さらに注意点としては,ASP1.xでは,ActiveX DLLのサブルーチンの引数に戻り値を設定するときは,引数としてバリアント型を指定しなければいけないことだ.これは,ASPの変数がすべてバリアント型であることが影響している.では,ASP側から値を設定するときには大丈夫かといえば,これもやはりバリアント型になっていないとASPで実行時エラーが発生する.ただし,ASP側から値を設定するような引数では,CLng(3)のように型変換関数で囲むことでも対応できる.
 ASPでもできることをどうして,Visual Basicで作成しなければならないのか.それは,

  1. 使い慣れた開発環境が利用可能
  2. ロジックをDLLに隠蔽可能
  3. APIの利用など
  4. 適切な変数型を使うことによりパフォーマンスが向上

などの利点があるからだ.(1)〜(3)の利点はすぐに思い浮かぶと思うが,問題は(4)の利点だ.テーブルの内容を表示するサンプル(リスト7)を作成してみたが,差はなかった.どうやら,VBにおけるネイティブコンパイルとPコードコンパイルの実行結果のように,複雑なロジックを実装して,初めて差が見えてくる類のもののようである.
 意外と知られていないが,Visual Basicで作成したActiveX DLLもレジストリに登録が必要だ.開発環境でVBPを開いて実行すれば自動的に登録される.また開発環境があれば,VBPを開かずにDLLファイルだけコピーして,

regsvr32 /c <DLL名>

とすればレジストリに登録できる.レジストリからの削除は,同様に

regsvr32 /u <dll名>

だ.このときは,登録したときと同じ場所にDLLが存在しなければならない.移動や削除前に上記コマンドの投入が必要だ.
 最後に,ASP2.x以降ではマルチスレッドによる動作をしているので,VB5.0(SP2以降)またはVB6.0でActiveX DLLを作成しないとパフォーマンスの低下を招くことになるので注意して欲しい.

リスト7 ASPからActiveX DLLを利用する
---ActiveX DLL側(OraSess.cls抜粋)---

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
  Persistable = 0  'NotPersistable
  DataBindingBehavior = 0  'vbNone
  DataSourceBehavior  = 0  'vbNone
  MTSTransactionMode  = 0  'NotAnMTSObject
END

Option Explicit
Private Const mcNUMDATABASE = 10
Private mobjSess                 As Object
Private maobjDb(mcNUMDATABASE)   As Object
Private malngStat(mcNUMDATABASE) As Long
Private mintNum                  As Integer

Private Sub Class_Initialize()
    Set mobjSess = CreateObject("OracleInProcServer.XOraSession")
End Sub

Private Sub Class_Terminate()
    Set mobjSess = Nothing
End Sub

Public Sub psubDbOpen(rstrHost As String, rstrConn As String, rintNum As Integer)
'   概要:あらかじめOracleにログオンしておく。
'   rstrHost    (IN)    データベース別名
'   rstrConn    (IN)    ユーザID/パスワード
'   rintNum     (IN)    プール数

    Dim iintLoop As Integer

'    mintNum = IIf(rintNum < mcNUMDATABASE, rintNum, mcNUMDATABASE) - 1

     Set maobjDb(iintLoop) = mobjSess.DbOpenDatabase(rstrHost, rstrConn, 0&)
     malngStat(iintLoop) = 0
End Sub

Public Function pobjDbGet(rlngSess As Long) As Object
'   概要:プーリングしていたセッションを一つリザーブする。
'   rlngSess    (IN)    ASPのセッションID
    Dim iintLoop    As Integer
    Dim blnRet      As Boolean

    Set pobjDbGet = maobjDb(iintLoop)
    malngStat(iintLoop) = rlngSess
    blnRet = True
    If Not blnRet Then
        Error 9999
    End If
End Function

Public Sub psubDbRel(rlngSess As Long)
'   概要:リザーブしていたセッションを一つ開放する。
'   rlngSess    (IN)    ASPのセッションID
    Dim iintLoop    As Integer

    For iintLoop = 0 To mcNUMDATABASE
        If malngStat(iintLoop) = rlngSess Then
            malngStat(iintLoop) = 0
            Exit For
        End If
    Next
End Sub

Public Function pstrTableGet(rlngSess As Long, rstrSQL As String) As String
'   概要:指定されたSQL文の結果をHTML表として返却する。
'   robjDb  (IN)    データベースオブジェクト
'   rstrSQL (IN)    SQL文
    Dim objDs       As Object
    Dim iintIndex   As Integer
    Dim iintLoop    As Integer
    Dim strResult   As String

    On Error GoTo errTableGet:

    strResult = "<table border=1>"

    Set objDs = maobjDb(iintIndex).DbCreateDynaset(rstrSQL, 12&)
    For iintLoop = 0 To objDs.Fields.Count - 1
        strResult = strResult & "<th>" & objDs(iintLoop).Name & "</th>"
    Next

    Do While Not objDs.EOF
        strResult = strResult & "<tr>"
        For iintLoop = 0 To objDs.Fields.Count - 1
            strResult = strResult & "<td>" & objDs(iintLoop).Value & "</td>"
        Next
        strResult = strResult & "</tr>"
        objDs.DbMoveNext
     Loop
     strResult = strResult & "</table>"

exitTableGet:
    On Error Resume Next
    Set objDs = Nothing
    pstrTableGet = strResult
    Exit Function

errTableGet:
    If maobjDb(iintIndex).LastServerErr = 0 Then
        strResult = "<HR><BR>" & Error$ & "<HR><BR>"
    Else
        strResult = "<HR><BR>" & maobjDb(iintIndex).LastServerErrText & "<HR><BR>"
        maobjDb(iintIndex).LastServerErrReset
    End If
    Resume exitTableGet:
End Function


---ASP側(AxDll.asp抜粋)---

<%@ LANGUAGE="VBSCRIPT" %>
<HTML>
<HEAD>
  <TITLE>Call ActiveX DLL for ASP 1.x</TITLE>
</HEAD>
<%
    strSQL = "SELECT * FROM emp"
    strUser = Trim(Request("USER_ID")) & "/" &  Trim(Request("PASSWORD"))
    strHost = Trim(Request("HOST"))
    Set Session("Session") = Server.CreateObject("OraSess.clsOraSess")
    Call Session("Session").psubDbOpen(CStr(strHost), CStr(strUser),CInt(1))
    Set objDb =  Session("Session").pobjDbGet(Session.SessionID)
    strHTML = Session("Session").pstrTableGet(Session.SessionID,CStr(strSQL))
	    Response.Write strHTML
    Call Session("Session").psubDbRel(Session.SessionID)
    Set Session("Session") = Nothing
%>
</BODY>
</HTML>

サンプルプログラム(28KB)


VB Magazine ライブラリ | Visual Basic WorkGroup
int21 ホームページ | PCDN ホームページ


Copyright (c) 1998 int21 Corporation All Rights Reserved.
For questions or comments, please send mail to: pcdn@int21.co.jp