初音 玲 HATSUNE, Akira
IISにおける
Webアプリケーションの基本
IISでWebアプリケーションを作るとき、どんな技術を用いたらよいのだろうか。過去の技術となったIDC/IDXをいまさら選択する人はいないだろう。Webサーバーに依存しないようにするためにCGI(Common Gateway Interface)を選択したり、Microsoft Script Debuggerなど、デバッグ環境が整備されたことに伴なってActive Server Pages(ASP)を採用する場合もあるだろう。また、Visual Basic 6.0から追加されたWebClassを使って、使い慣れたVisual Basic(以下VB)の開発環境で、WebアプリケーションをIISアプリケーションとして作成することを選ぶかもしれない。これらの方法とISAPIではどのような点が異なるのだろうか。
CGIの概要
CGIは、外部プログラムを呼び出す技術のひとつで、ほとんどのWebサーバーでサポートされている。また、Perlなどのスクリプト言語で記述することが多いので、CGIスクリプトなどと呼ばれるときもある。もちろん、C言語などで記述することも可能で、要は、標準入出力をサポートしているのであれば、どんな言語でもCGIを作成することができる(図1)。
図1:CGIアプリケーションの流れ
このCGIをVBで作成しようと考えると、なかなかやっかいな問題が発生する。それは、Visual Basicには標準入出力先とのI/Oを行なう機能が備わっていないのだ。そこで、Win32 APIが活躍する。使うAPIは、
- GetStdHandle API
- WriteFile API
- ReadFile API
の3つだ。Webクライアントからの入力がないのであれば、GetStdHandle APIとWriteFile APIだけでも作成できる(サンプルCGI、リスト1)。
リスト1:VBで作成したCGIのサンプルプログラム
Option Explicit Declare Function GetStdHandle Lib "kernel32" _ (ByVal nStdHandle As Long) As Long Declare Function WriteFile Lib "kernel32" _ (ByVal hFile As Long, _ lpBuffer As Any, _ ByVal nNumberOfBytesToWrite As Long, _ lpNumberOfBytesWritten As Long, _ ByVal lpOverlapped As Long) As Long Declare Function ReadFile Lib "kernel32" _ (ByVal hFile As Long, _ lpBuffer As Any, _ ByVal nNumberOfBytesToRead As Long, _ lpNumberOfBytesRead As Long, _ lpOverlapped As Any) As Long Public Const STD_INPUT_HANDLE As Long = -10& Public Const STD_OUTPUT_HANDLE As Long = -11& Public Const STD_ERROR_HANDLE As Long = -12& Private Sub Main() On Error GoTo errMain: ' 応答コード部 printf "HTTP/1.0 200 OK" & vbCrLf ' ヘッダ部 printf "Content-type: text/html" & vbCrLf & vbCrLf printf "<HTML>" & vbCrLf printf "<HEAD><TITLE>Hello, World! _ </TITLE></HEAD>" & vbCrLf printf "<BODY>こんにちは、世界!</BODY>" & vbCrLf exitMain: On Error Resume Next printf "</HTML>" & vbCrLf Exit Sub errMain: printf Error$ Resume exitMain: End Sub Private Sub printf(ByVal strBuffer As String) Dim abytBuffer() As Byte Dim lngOutLen As Long Dim lngBytes As Long Dim lngRet As Long Dim hStdOut As Long ' VBの内部コードであるUnicodeからSJISに変換して ' それをバイナリとして扱う abytBuffer = StrConv(strBuffer, vbFromUnicode) ' バイト配列の大きさ(いわゆる文字列長)を求める lngOutLen = UBound(abytBuffer) - _ Bound(abytBuffer) + 1 ' 標準出力ハンドルを取得する hStdOut = GetStdHandle(STD_OUTPUT_HANDLE) ' 標準出力先へSJISコードのまま出力する lngRet = WriteFile(hStdOut, _ abytBuffer(0), _ lngOutLen, _ lngBytes, _ 0&) End SubWriteFile APIを使うときには、UnicodeとシフトJISの関係に注意を払う必要がある。それは、VBの32bit版では、Windowsと同様に文字の内部コードとしてUnicodeが使われているためだ。要は、実際にVBが意識しているのは、シフトJISではなくUnicodeだということだ。もちろん、ファイル入出力などでは、VB自体が外部コードであるシフトJISに変換しているので、意識する必要がない。しかし、APIを使うときには、文字列をUnicodeとして渡すのか、それともシフトJISに変換して渡すのか、きちんと意識しなければならない。今回のサンプルでも、WriteFile APIに文字列を渡す前に、シフトJISにコード変換している。コード変換したものを文字列に入れようとすると、そこでシフトJIS→Unicode変換が自動的に起こってしまうので、バイト型配列に代入して、自動コード変換が発生しないようにしている。
このようにして作成した標準EXEをIISの仮想ディレクトリのどこか(たとえばSCRIPTSディレクトリ)にコピーして、その仮想ディレクトリのプロパティの設定で、アクセス権として「実行」を許可すればよい。そして、Webブラウザより、
http://WebServer/SCRIPT/CGI.exe
のようなURLでサンプルプログラムを指定すれば、ブラウザには
こんにちは、世界!
と表示される(リスト2)。
リスト2:CGIによって出力されたHTML
<HTML> <HEAD><TITLE>Hello, World!</TITLE></HEAD> <BODY>こんにちは、世界!</BODY> </HTML>
CGIの問題点
CGIの問題点としては、ブラウザから依頼された単位でCGIの起動と終了が起こることだ。そのため、アクセスが集中したときにレスポンスが低下しやすい。
また、VBでCGIを作ったときもそうだが、Windows環境では、デバッグするのが面倒な点も問題になってくる。これは、UNIXなどと異なり、WindowsではDOS窓などで標準入出力をサポートしていないことが原因だ。
VBでCGI
VBでCGIを作る利点は、余りないように感じる人も多いだろう。確かに、CGIの移植性を損なってまでVBに固執する必要はないかもしれない。しかし、Visual Basicのノウハウやコードなどが蓄積されているのならば、VBによるCGIを検討してみる価値はあるだろう。しかも、Web ClassなどのようにVB6.0以降でのみ使える新技術ではないので、VB4.0(32bit)以降ならば、どのバージョンでも作成することができる。また、変数がすべてバリアント型であるASPのように、処理速度が低下したり、型合わせのためにプログラムの手間がかかったりすることもない。
試しに、Oracle Objects for OLE(以下oo4o)を使って、Oracleデータベースからデータを取得して表示するサンプルを作成したみた(リスト3、4)。見なれたコードのままで、Webアプリケーションを作成できるということが一目瞭然だ。oo4oからのエラーについても、HTMLのBODY部に文字列として埋め込んでしまえばよいだろう。もちろん、RDO用に書き換えてSQL ServerをRDBMSとすることも可能だ。このときは、SQL ServerやODBCドライバからのエラーに半角カナ文字が含まれているので、StrConv関数を使って、全角文字に置き換える必要がある。もっとも、そうすると英字も全角になって、カッコ悪いエラー表示画面になってしまうが、それは仕方がないことだろう。
なお、デバッグについても、標準出力に送る文字列を「Debug.Print」を使って表示することで比較的簡単に行なうことができる(リスト5)。極めて単純な方法だが、VBのIDEを使ってのデバッグもスムーズに実施できるはずだ。
リスト3:Oracleデータベースからデータを取得して表示するCGIのサンプル
Private Sub Main() Dim oraSes As Object ' oo4oとのセッション Dim oraDb As Object ' RDBMSとのセッション Dim oraDs As Object ' SELECT集合 Dim strErrText As String ' エラー文字列 Dim intCol As Integer ' フィールド位置 Dim strSQL As String ' SQL文 On Error GoTo errMain: printf "HTTP/1.0 200 OK" & vbCrLf ' 応答コード部 printf "Content-type: text/html" & vbCrLf & vbCrLf ' ヘッダ部 printf "<HTML>" & vbCrLf printf "<HEAD><TITLE> _ CGI for oo4o Sample Program</TITLE> _ </HEAD>" & vbCrLf printf "<BODY>" & vbCrLf ' 検索SQL文の設定 strSQL = "SELECT * FROM EMP ORDER BY EMPNO" ' oo4oと接続 Set oraSes = CreateObject("OracleInProcServer.XOraSession") ' RDBMSと接続 '(データベース別名:kawa8、ユーザーID:scott、パスワード:tigerの例) Set oraDb = oraSes.Opendatabase("kawa8", "scott/tiger", 0&) ' SELECT文発行 Set oraDs = oraDb.DbCreateDynaset(strSQL, 12&) ' HTMLの組み立て printf "<TABLE BORDER=1>" & vbCrLf printf "<tr>" & vbCrLf For intCol = 0 To oraDs.fields.Count - 1 printf "<th>" & oraDs(intCol).Name _ & "</th>" & vbCrLf Next printf "</tr>" & vbCrLf Do While Not oraDs.EOF printf "<tr>" & vbCrLf For intCol = 0 To oraDs.fields.Count - 1 If Not IsNull(oraDs(intCol)) Then printf "<td>" & oraDs(intCol).Value _ & "</td>" & vbCrLf Else printf "<td>(null)</td>" & vbCrLf End If Next printf "</tr>" & vbCrLf oraDs.DbMoveNext Loop printf "</TABLE>" & vbCrLf exitMain: On Error Resume Next Set oraDs = Nothing Set oraDb = Nothing Set oraSes = Nothing printf "</BODY>" & vbCrLf printf "</HTML>" & vbCrLf Exit Sub errMain: If oraSes.LastServerErr = 0 Then If oraDb.LastServerErr = 0 Then strErrText = Error$ Else strErrText = oraDb.LastServerErrText oraDb.LastServerErrReset End If Else strErrText = oraSes.LastServerErrText oraSes.LastServerErrReset End If printf strErrText & vbCrLf Resume exitMain: End Subリスト4:リスト3のCGIによって生成されたHTMLの例
<HTML> <HEAD><TITLE>CGI for oo4o Sample Program</TITLE></HEAD> <BODY> <TABLE BORDER=1> <tr> <th>EMPNO</th> <th>ENAME</th> <th>JOB</th> <th>MGR</th> <th>HIREDATE</th> <th>SAL</th> <th>COMM</th> <th>DEPTNO</th> </tr> <tr> <td>7369</td> <td>SMITH</td> <td>CLERK</td> <td>7902</td> <td>80/12/17</td> <td>800</td> <td>(null)</td> <td>20</td> </tr> :(中略) <tr> <td>7934</td> <td>MILLER</td> <td>CLERK</td> <td>7782</td> <td>82/01/23</td> <td>1300</td> <td>(null)</td> <td>10</td> </tr> <tr> <td>7935</td> <td>AKIRA</td> <td>MANAGER</td> <td>7839</td> <td>(null)</td> <td>100</td> <td>(null)</td> <td>10</td> </tr> </TABLE> </BODY> </HTML>リスト5:デバッグ用のコードを埋め込む
Private Sub printf(ByVal strBuffer As String) Dim abytBuffer() As Byte Dim lngOutLen As Long Dim lngBytes As Long Dim lngRet As Long Dim hStdOut As Long ' デバッグ用のコード Debug.Print strBuffer; ' VBの内部コードであるUnicodeからSJISに変換してそれをバイナ 'リとして扱う abytBuffer = StrConv(strBuffer, vbFromUnicode) ' バイト配列の大きさ(いわゆる文字列長)を求める lngOutLen = UBound(abytBuffer) - LBound(abytBuffer) + 1 ' 標準出力ハンドルを取得する hStdOut = GetStdHandle(STD_OUTPUT_HANDLE) ' 標準出力先へSJISコードのまま出力する lngRet = WriteFile(hStdOut, _ abytBuffer(0), _ lngOutLen, _ lngBytes, _ 0&) End Sub
VBでCGI
標準入力を活用する
さて、VBによるCGIのサンプルをもう1点紹介する。ブラウザからの入力をサポートした例だ(サンプルCGIStdIn)。ブラウザからの入力を判定には、大きく分けて2通りの方法がある。環境変数を参照する方法と標準入力からデータを受け取る方法だ。
環境変数を参照する
CGIがサポートされていれば、ある程度標準的な環境変数にブラウザからの情報が格納される。一般的な環境変数を表1にまとめてみた。この環境変数をVBで参照するには、Environ関数を使う(リスト6)。
もしCGIアプリケーションが、HTMLフォームの情報をGETメソッドで受け取るならば、フォームに設定した情報は、環境変数[QUERY_STRING]に設定される。しかし、GETメソッドを使う形式では、URLにその情報が表示されてしまう。これは、格好が悪いだけではなく、ダイレクトにパラメタ付きでCGIを起動することができるということになる。これでは、具合の悪いこともある。このようなことを回避するためには、HTMLフォームからCGIを起動するもうひとつの方法、POSTメソッドを使う。そして、そのときに情報を受け取る方法が、標準入力なのだ。
リスト6:Environ関数を使って環境変数を参照する
printf "AUTH_TYPE=" & Environ("AUTH_TYPE") & "<br>" & vbCrLf printf "CONTENT_LENGTH=" & Environ("CONTENT_LENGTH") & "<br>" & vbCrLf printf "CONTENT_TYPE=" & Environ("CONTENT_TYPE") & "<br>" & vbCrLf printf "REMOTE_ADDR=" & Environ("REMOTE_ADDR") & "<br>" & vbCrLf printf "REMOTE_HOST=" & Environ("REMOTE_HOST") & "<br>" & vbCrLf printf "REMOTE_USER=" & Environ("REMOTE_USER") & "<br>" & vbCrLf printf "REQUEST_METHOD=" & Environ("REQUEST_METHOD") & "<br>" & vbCrLf printf "SCRIPT_NAME=" & Environ("SCRIPT_NAME") & "<br>" & vbCrLf printf "SERVER_NAME=" & Environ("SERVER_NAME") & "<br>" & vbCrLf printf "SERVER_PORT=" & Environ("SERVER_PORT") & "<br>" & vbCrLf printf "URL=" & Environ("URL") & "<br>" & vbCrLf表1:CGI環境変数
変数名 意味 AUTH_TYPE 認証タイプ CONTENT_LENGTH POSTメソッドで渡されるデータのバイト数 CONTENT_TYPE POSTメソッドで渡される情報の種類 REMOTE_ADDR クライアントのIPアドレス REMOTE_HOST クライアントのホスト名 REMOTE_USER ユーザー名 REQUEST_METHOD 要求メソッド(POSTやGET) SCRIPT_NAME スクリプト名 SERVER_NAME サーバー名 SERVER_PORT 要求を受け取ったポート番号 URL 要求されたURL
標準入力からデータを受け取る
表1にあるように、標準入力で受け取れるデータが存在するかは、[CONTENT_LENGTH]環境変数の値を参照すればよい。そして、実際に標準入力からデータを受け取るには、GetStdHandle APIとReadFile APIを使う(リスト7)。
リスト7:標準入力からデータを受け取る
Private Function getchar() As String Dim abytBuffer() As Byte Dim lngInLen As Long Dim lngBytes As Long Dim hStdIn As Long Dim lngRet As Long Dim strBuffer As String On Error GoTo errChar: lngInLen = Environ("CONTENT_LENGTH") If lngInLen > 0 Then ReDim abytBuffer(lngInLen) strBuffer = String(lngInLen, Chr$(0)) hStdIn = GetStdHandle(STD_INPUT_HANDLE) lngRet = ReadFile(hStdIn, _ ByVal strBuffer, _ lngInLen, _ lngBytes, _ ByVal 0&) getchar = Left$(strBuffer, lngBytes) Else getchar = "" End If exitChar: On Error Resume Next Exit Function errChar: printf Error$ & vbCrLf Resume exitChar: Resume Next End Functionさて、問題なのは、標準入力から受け取った情報が、シフトJISコード系にはなっていないという点だ。そこで、Visual Basicが理解できるコード系に変換する必要がある(図2)。それが、URLDecode関数だ(リスト8)。VB6.0からはこのような処理を行なうのに便利な機能が追加されている。これら新機能を使わない手はないのだが、今回はVB4.0/5.0でのプログラムの作成も考えて、条件コンパイル定数により、複数バージョンに対応したソースコードにしている。VB6.0以外の方は、CGIStdIn.basファイルの先頭にある
#Const VB6 = True
を削除して欲しい。
このようにして作成したCGIアプリケーションを呼び出すためには、リスト8のようにHTMLフォームを使って、POSTすればよい。
図2:コード変換のアルゴリズム
リスト8:標準入力から渡された情報をVBが理解できるコード系に変換する
Private Function strURLDecode(ByVal rstrPost As String) _ As String Dim intindex As Integer Dim intPos As Integer Dim strAsc As String ' 8bit Decode intindex = 1 strURLDecode = "" intPos = InStr(rstrPost, "%") Do While intPos > 0 If intPos > 1 Then strURLDecode = strURLDecode _ & Mid$(rstrPost, intindex, intPos - 1) intindex = intindex + intPos Else intindex = intindex + 1 End If strAsc = StrConv(Mid$(rstrPost, intindex, 2), _ vbUpperCase) intindex = intindex + 2 If strAsc > "7F" Then ' 漢字項目なので If Mid$(rstrPost, intindex, 1) = "%" Then strAsc = Chr(CInt("&H" & strAsc _ & Mid$(rstrPost, intindex + 1, 2))) intindex = intindex + 3 Else strAsc = Chr$(CInt("&H" & strAsc _ & Hex(Asc(Mid$(rstrPost, intindex, _ 1))))) intindex = intindex + 1 End If Else strAsc = Chr$(CInt("&H" & strAsc)) End If strURLDecode = strURLDecode & strAsc intPos = InStr(Mid$(rstrPost, intindex), "%") Loop strURLDecode = strURLDecode & Mid$(rstrPost, intindex) ' + Decode strAsc = strURLDecode#If VB6 Then strURLDecode = Replace(strAsc, "+", " ", _ 1, -1, vbTextCompare) #Else intPos = InStr(strAsc, "+") Do While intPos > 0 Mid$(strAsc, intPos, 1) = " " intPos = InStr(strAsc, "+") Loop strURLDecode = strAsc #End If End Function Private Function strFindKeyValue(rstrQuery As String, _ rstrKey As String) As String Dim astrTmp() As String Dim iintLoop As Integer Dim intPos As Integer strFindKeyValue = "" #If VB6 Then astrTmp() = Split(rstrQuery, "&", -1, vbTextCompare) For iintLoop = 0 To UBound(astrTmp) intPos = InStr(astrTmp(iintLoop), rstrKey & "=") If intPos > 0 Then strFindKeyValue = Mid$(astrTmp(iintLoop), _ intPos + Len(rstrKey & "=")) End If Next #Else If InStr(astrTmp(iintLoop), rstrKey & "=") > 1 Then intPos = InStr(astrTmp(iintLoop), "&" & _ rstrKey & "=") strFindKeyValue = Mid$(rstrQuery, _ intPos + Len("&" & rstrKey & "=")) Else strFindKeyValue = Left$(rstrQuery, _ Len(rstrKey & "=")) End If #End If End Function
確かにサンプル3は、VBのバージョンに依存しないつくりにはなっている。しかし、お使いのIISやPWS(Personal Web Server)のバージョン、OSのバージョンやサービスパックが、VBのバージョンと整合性があるかをきちんと判断した方がよいだろう
さらにもう一工夫
サンプル3では、CGIアプリケーションが呼び出されるたびに、Oracleへの接続と切断が行なわれる。これはとても非効率的だ。そこで、前号の特集で取り上げたMTS(Microsoft Transaction Server)を活用するとよいだろう。MTSのコネクションプーリング機能を使って、CGIアプリケーションの起動や終了とは無関係に、DCOMで通信する先のMTSオブジェクトは、Oracleとの接続を確保しつづけてくれる(図3)。
図3:CGIとMTSを組み合わせた例
IISでCGI利用の注意点
認証を行なわないでアクセスしている(匿名アクセス)と、IISが稼動しているOSは「IUSR_サーバー名」アカウントのユーザーとして、アクセスしている人を認識している。この「IUSR_サーバー名」アカウントには、IISインストール直後は、ローカルログオン権限とゲスト権限しか割り当てられていない。一見、ある程度のセキュリティを確保できているように見えるが、フォルダやファイルに対して、「Everyone」の権限が設定されていると、匿名アクセスのユーザーからも操作が可能になってしまう。
- 「IUSR_サーバー名」に余計な権限を与えない
- 「Everyone」の権限が設定されているフォルダやファイルには、「IUSR_サーバー名」のアクセス権として「アクセス権なし」を指定する
などの配慮が必要になってくる。最悪の場合、IIS実行権があるフォルダに対して、[他のファイルを変更する]CGIをアップロードして、システムを破壊することだって可能なのだ。
これは、CGIを使ったときだけではなく、NTFSのデフォルト値なども絡んだWindows NTセキュリティモデル全体の問題だ。Windows NTをインターネットに晒すべきではないという意見は、セキュリティホールがあるという以前に、システムのデフォルト値の甘さに端を発している。
ISAPIアプリケーション概要
ISAPIアプリケーションには、ISAPIサーバー拡張とISAPIフィルタの2種類の形態がある。その両者に共通しているのは、DLL形式のプログラム(ISAIアプリケーション)とIISを繋ぐ技術だと言うことだ。よって、ISAPIアプリケーションを使ったときの利点として、CGIで問題になってくるプロセス生成のためのオーバーヘッドが軽減されることがある。このプロセス生成のオーバーヘッドは、ActiveX DLLとして動作するASPでも同様に軽減できるので、ISAPのみの利点ではない。しかし、そのつどスクリプトを解釈しながら動作するASPに比べ、ネイティブコンパイル後のDLLであるISAPIアプリケーションでは、ロジック実行のオーバーヘッドが軽減される。
つまりISAPIアプリケーションは、ASPの利点(少ないオーバーヘッドでプロセスを起動可能)とCGIの利点(少ないオーバヘッドでロジックを実行可能)の両方を同時に実現できるのだ。
ISAPIアプリケーション
〜ISAPIサーバー拡張〜
ISAPIサーバー拡張は、先ほどのCGIアプリケーションのように、フォームに値を記入(もちろん、URLに直接指定することもできる)して、[Submit]ボタンをクリックすることで起動する(図4)。
図4:ISAPIアプリケーションの流れ
このISAPIサーバー拡張をVisual Basicで作成するときは、越えなくてはならない大きな山がある。それは、"ISAPIサーバー拡張は、DLL形式の実行ファイルとしてコンパイルしなければならない"点だ。もちろん、ここで言うDLL形式とは、COMインターフェイスを使ったActiveX DLLではなく、純粋なDLLだ。この形式の実行ファイルをVBで作ることはできない。そこで考えられるのが、DLL形式の実行ファイルをVisual C++で作成して、その中からCOMを使ってVBで作成したActiveX DLLなりActiveX EXEを呼び出すという方法だろう(図5)。
図5:ISAPIサーバー拡張をVisual Basicで作るには
そこまでして、VBで作る必要があるかというもっともな疑問も生じる。確かに何もない状態から、図5の「橋渡しDLL」を自作するのならば、そのままVisual C++を使ってISAPIサーバー拡張の作成に邁進した方が幸せになれる。
しかし、「橋渡しDLL」が市販されていたらどうだろうか。これならば、VBでISAPIサーバー拡張を作成する価値が俄然出てくるであろう。
勘のよい方は気づかれたかもしれないが、「橋渡しDLL」は市販されている。そして、市販されているからこそ、今回の特集でISAPIを取り上げようと思ったのだ。
特に今回取り上げる2つの製品は、テスト方法にまで気を使った製品で、テスト効率の面からも、標準入力を使ったCGIアプリケーションよりも性能が高い。
ISAPIアプリケーション
〜Web Extender〜
まずは、まさに「橋渡しDLL」のアイデアそのものを製品化したようなアグレッシブ株式会社の「Web Extender」を紹介する(http://www.aie.ne.jp/extender/)。
Web Extenderの本体は、ISAPIサーバー拡張であるGWIISOLE.DLLだ。URLで、GWIISOLE.DLLに引き続いて、
[ActiveXDLL名].[class名].[method名]
と記述すれば、GWIIDOLE.DLLは、指定されたActiveX DLLのクラスのメソッドをCOMを経由して呼び出す(図6)。このメソッドは、名前こそ自由に付けることができるが、パラメータの個数や型は決まっている。メソッド名としてActionを指定したときは、該当クラスの中に、
Public Sub Action(request As String, _
Response As String)
と記述して、Web Extenderからの出入り口を定義する。とくに第二パラメータは重要で、メソッドの戻り値として使用される。この戻り値は、Web Extender上でHTMLとみなされ、それをブラウザに送り返すことになる。要は、HTMLを標準出力に渡す変わりに、変数に設定してGWIISOLE.DLLに返却すれば良いということだ。
また、Web Extenderの面白いところは、クラスが呼び出されるときに、CGI環境変数の値を同名のPublic変数に設定する点だ。たとえば[QUERY_STRING]環境変数の値が欲しいときは、クラスの変数宣言部に、
Public QUERY_STRING As String
と変数宣言をしておけばよいのだ。これは便利な機能だ。
なお、テスト方法としては、GWIISOLE.DLLの代わりに製品に付属しているテストクライアントを使って、ActiveX DLLを呼び出してテストをする。もちろん、事前にバイナリ互換をもったVBのIDEでソースプログラムを実行しておいて、テストクライアントとIDEを連携してステップ実行などを行ないながらテストすることもできる。*1
図6:Web Extenderの動作原理
ISAPIアプリケーション
〜VB-WEB〜
もう一本の製品は、アドバンス技研株式会社の「VB-WEB」だ(http://www.adv.co.jp/)。この製品のアプローチは、「橋渡しDLL」+αという感じだ(図7)。Visual Basicでは、ActiveX DLLやActiveX EXEではなく、標準EXEとして実行ファイルを作成する。そして、その標準EXEをISAPIサーバー拡張であるVB_Bridge.DLLから起動する。このとき、インターフェイスにはすべてファイルを使っている。雰囲気としては、よりCGIに近い形式だろう。VB-Webの利点は、V Bで作ったロジックに障害があっても、IISが巻き添えを食う確率がCGI並みに低いということだ。もちろんそのために純粋な「橋渡しDLL」の方式よりもプロセス生成のオーバヘッドが存在する。しかし、安全性を確保したいという向きにはお勧めの方式だろう。そして、Win32APIを使ってCGIアプリケーションを作成するよりも、デバッグ環境が整備されているという点も見逃せない。
VB-WEBを使うときには、URLで指定するのではなく、必ずHTMLフォームを使う。<FORM>タグのACTIONにVB_Bridge.DLLを指定して、INPUTタグのNAMEに"VBEXE"、VALUEに標準EXEの実ディレクトリにおける位置をフルパスで指定する。このVALUEへの指定方法だけはいただけない。なんとかIISの仮想ディレクトリに対応して欲しいものだ。
さて、VB-WEBのテスト方法もユニークだ。VB_Bridge.DLLではなくVB_Bridge2.DLLを指定することで、捜査結果をファイルに出力する。そのファイルを基にIIS環境がないところでもVBのIDEのみでテストおよびデバッグが可能になる。一見非効率に見えるが、実際に運用を行なっているようなときには、効率的な方法だ。
図7:ISAPIサーバー拡張をVisual Basicで作るには
ISAPIアプリケーション
まとめ
今回2つの製品を紹介したが、同じような技術を使いつつ、VBで作成したロジックのプロセスをどのように扱うかに関しては、別々の回答を導き出している。それぞれの回答のどちらがよいかは、作成する業務により異なってくるだろう。安全性とパフォーマンスのバランスを考慮しながら判断して欲しい。なお、どちらの製品を用いても、ASPよりもパフォーマンスが高いことをお伝えしておく。
ISAPIアプリケーション
〜ISAPIフィルタ〜
ISAPIフィルタは、プログラムをサブクラス化してWindowsメッセージをフックするように、IISとクライアントの間のHTTPプロトコルをフックする。このISAPIフィルタこそ、CGIやASPでは実現できないISAPIの独自性だ。
そして、ISAPIフィルタを使うことで、ユーザーごとのホームページを
http://server/~userid/
という「~(チルダ)」付きのURLで管理することができる。つまり、「~」があればuseridに対応したホームディレクトリのpublic_htmlに位置付けすればよい。
ただし、残念なことに、前記2製品をもってしても、ISAPIフィルタをVBで作成することはできない。そもそも、無理やりVBでプログラムしなくても、Visual C++のISAPI Extension Wizardを使って、自動的にISAPIフィルタのイベントごとの関数定義を生成することができる。そしてあとは、イベントに対応した関数定義の中にロジックを記述すればよい。ISAPIフィルタのようにAPI中心のプログラムならば、Visual C++の方が適しているし、MFCなどのライブラリをスタティックリンクして、ひとつの実行ファイルとしてコンパイルすることもできる。
このスタティックリンクは、実行ファイルのサイズが大きくなるという不利な点もあるが、その他のDLLなどが不要になるため、DLLの不整合により動作不良になったり、または、自身をインストールする際に、互換性のない同名DLLをインストールしてしまい環境を壊すといったこともない。年々、パソコンのメモリやHDDが安価で高性能になってきているので、スタティックリンクの弊害は、ほとんど影響を及ぼさなくなっているのも朗報だ。きちんとした市販アプリケーションを販売しているところでは、このスタティックリンクを行なっていることが多いようである。VBの次版では、ぜひスタティックリンクを実装して欲しいと思う。そのためには、肥大化したVBのランタイムDLLを、どう分割するかが問題になってくるだろう。
最後に
冒頭で列挙したWebアプリケーションの作り方にはひとつの法則がある。それは、IDC/IDX、CGI、ASPおよびISAPIは、すべてサーバーサイドで動作する仕組みだということだ。つまり、Webサーバー内部でブラウザからの要求に基づいてHTMLを組み立ててそれを返却するということだ。これがWebアプリケーション開発の成功のカギだ。
サーバーサイドで標準的なHTMLを出力することにより、ブラウザが解釈する部分を極力抑えることで、ブラウザ間の互換性のなさを吸収して、ある程度ブラウザに依存しないアプリケーションが作れる。さらにどうしてもブラウザに依存した処理を記述しなければいけないときは、クライアントサイドスクリプト(VBSctiptやJScript、Java Script、DHTML、スタイルシートなど)でブラウザを判断するのではなく、サーバーで判断してブラウザにあったHTMLを出力することも可能だ。こうすることで、"自分のブラウザでは必要ないコードが送られてくるために、ネットワークトラフィックが増大したり、Webページが開くまでに時間が余計にかかったり"といったことがなくなるのだ。
よって、ホームページを作っている自社社員に片手間で任せられる安易で安価な構築法として、Webアプリケーション構築を捉えるべきではないだろう。プロバイダに個人でホームページを作るときには、プロバイダのセキュリティ方針などで、サーバーサイドでの実行が著しく制限されていることが多い。そのため、勢いクライアントサイドスクリプトに頼るしかない。同じような技術ではあるが、そこで使われている手法は全然異なるのだ。きっちりした設計と作成技術をもった個人なり会社に協力を仰ぐべきだ。そして、他の言語に比べて、HTMLは可読性と覚えやすさが突出している言語なのだから、その後のメインテナンスなどは、自社で行なっていけばよい。それが、Webアプリケーションの利点だ。
*1Web Extenderで少々残念な点は、VB 5.0 のランタイムをインストールしようとする点 だ。ランタイムをインストールしない選択肢 が欲しかったサンプルプログラムのダウンロード --- isapi.lzh(27KB)