Home |  バックナンバー |  ソースコード |  ニュース |  MSDN Magazine別冊 |
 MSDN Magazine 日本語版     

マイクロソフトテクノロジー エンサイクロペディア DVD-ROM版
  Back Issues Index
 2000
 2001
 2002
 2003
Source Code Index
MSDN Magazine 別冊
MSDN magazine US site
Microsoft.NETシリーズ 第4弾!
C#で始めるプログラミング「導入編」
C#で始めるプログラミング「導入編」
2002年10月18日発売!

Sample Code
 
C#で始めるプログラミング「オブジェクト指向編」
C#で始めるプログラミング「オブジェクト指向編」
2002年10月18日発売!

Sample Code
 
Microsoft.NETシリーズ 第3弾!
Microsoft.NET実践プログラミング - Best of MSDN Magazine 2001 -
Microsoft.NET実践プログラミング - Best of MSDN Magazine 2001 -
2002年7月2日発売!

Amazon.co.jpから御購入いただけます
 
Microsoft.NETシリーズ 第2弾!
Visual Basic.NETオブジェクト指向プログラミング入門
Visual Basic.NETオブジェクト指向プログラミング入門
2002年6月13日発売!

Amazon.co.jpから御購入いただけます
 
Microsoft.NETシリーズ 第1弾!
ASP.NETプログラマためのXMLテクニック
ASP.NETプログラマためのXMLテクニック
2002年3月1日発売!

Amazon.co.jpから御購入いただけます
 
msdn
September 2002 No.30 (2002年8月17日発売)
特集2 .NET実践プログラミング PartVII
ASP.NET
ISAPIとASP.NETのもとでHTTPフィルタを使ってWeb要求を横取り,監視,変更する

ASP.NET: Intercept, Monitor, and Modify Web Requests with HTTP Filters in ISAPI and ASP.NET
(August 2002 Vol.17 No.8)
Panos Kougiouris

Level :1 2 3
本記事は,C,C#,IIS,およびISAPIについての知識があることを前提に書かれています。
SUMMARY:送られてきたWeb要求のリルートにはさまざまな理由がある。たとえば,URLで長い引数リストを渡さずに,ユーザーの基準に基づいてブラウザを特定のページにリダイレクトしなければならない場合がある。従来,そのようなページ要求を横取りして別のページにリダイレクトするためのには,ISAPIを使うしかなかった。現在のASP.NETでは,IHttpModuleインターフェイスがサーバ要求の通知を提供し,ブラウザのタイプやバージョン以外の基準に基づいて簡単に要求をリルートできる。本稿は,IHttpModuleを使った横取りを具体的に示すとともに,ASP.NETをまだ使っていないユーザーのために,ISAPIフィルタの使い方も説明する。



 ASP.NETは,ASPと比べてさまざまな長所を持っている。強く型付けされた言語,コードビハインド機能,Webコントロールなどは,それらの長所のごく一部である。ASP.NETで私が気に入っていることの1つは,HTTP呼び出しを簡単にフィルタリングできるところである。これを利用すれば,Webプログラマは,すべての呼び出しとWebサーバからの応答を横取りし,要求が完結する前にコードを注入できる。HTTPフィルタが役に立つ場面としては,サイトのセキュリティ強化,スループットの記録などが挙げられる。

 ASP.NETが登場するまで,HTTP呼び出しをフィルタリングするための方法は,ISAPIフィルタを使うというものだったが,ISAPIフィルタは書くのが難しかった。また,IIS(Microsoft Internet Information Services)メタベースを書き換えなければならないので,ISAPIフィルタはインストールも複雑だった。そして,フィルタを開発,インストールするためには,IISプロセスを終了し,再起動しなければならない。ASP.NETは,ほとんどの場合,これらすべての問題を解決する。

 本稿では,HTTPフィルタリングを使って,URLの書き換えという一般的な問題を解決するための方法を示す。まず,ASP 3.0での問題の解決方法を示してから,ASP.NETを使って同じ問題を解決する。

URLの書き換え
 ある企業がhttp://www.bestPayroll.comという架空のWebベース給与計算サイトを運営しており,複数の顧客企業がこのサイトを使っているものとする。すべての企業は同じWebアプリケーションを使っているが,アプリケーションは企業ごとにカスタマイズされている。このアプリケーションの要件のなかでも特に重要なのは,個々の顧客企業がそれぞれの画面を直接見られるようにすることである。給与計算会社のASPプログラマたちは,個々の顧客企業に社名パラメータを含むURLを与えることにした。たとえば,架空のMSDN Magazine社のレポートページのURLは,http://www.bestPayroll.com/report.asp?company=msdnmagである。

 プログラマたちがこの解決方法を社内のビジネスアナリストに見せたとき,彼女はよい顔をしなかった。彼女は経験を積んだアナリストで,複数の.comベンチャーを手がけており,最近別のベンチャーでASPからASP.NETへの移行にかなり苦労したことを覚えていた。技術部門の人々はみな,これは技術面での変更であり,顧客には目に見える変化はないということを請け合った。しかし,土壇場になって,すべてのURLが.aspから.aspxに切り替わったために,ブックマークが機能しなくなったと顧客たちが不平を言っていたことを彼女は思い出した。そこで,彼女は.aspだの.aspxだの.jspだのといった専門用語に関心を示さないのだった。また,彼女はプログラマたちが提案したURLの末尾のクエスチョンマークを取り除きたいと思っていた。彼女は,プログラマたちに,URLとしてまったく異なる構文(http://www.yourPayroll.com/msdnmag/report)を使うように提案した。

 プログラマたちは,企業ごとに1つのディレクトリを使ういくつかの醜くてあまりスケーラブルではない解決方法を検討したあと,HTTPフィルタリングが最良の解決方法だということに気づいた。リクエストが届いたとき,仮想パスは,/msdnmag/reportのような文字列を含んでいてよい。フィルタがこれを/report.aspや/report.aspxに変換するのである。残された問題は,URLが要求されるたびに社名をテンプレートに知らせるにはどうしたらよいかということだけだった。

複数のディレクトリとデフォルトドキュメント
 ASPでも,ASP.NETでも,この問題を解決するための1つの方法は,IISデフォルトドキュメントを利用するというものである。Webサイトや仮想ディレクトリは,デフォルトドキュメントとして使われるいくつものファイル名を指定することができる。要求が届くと,サーバはディレクトリ内でデフォルトドキュメント名を持つファイルを探す。存在する場合には,そのドキュメントを返す。

 IISは,デフォルトドキュメント名としてdefault.htm,default.asp,default.aspxを使う。IISメタベースの継承機能を使えば,このリストは,サーバ全体で,あるいはサーバ内のサイトや単一の仮想ディレクトリ内で書き換えることができる。ユーザーは,,MMC(Microsoft Management Console)GUIを使うか,プログラム内でIIS Administration APIを使ってこのリストを書き換えることができる。

 デフォルトドキュメントを使うということは,表示するすべてのページについて,フォルダを作ってデフォルト名を持つページを作らなければならないということである。たとえば,先ほどのwww.bestPayroll.comの場合には,/reportというフォルダを作り,そのフォルダのなかにdefault.aspxページを追加しなければならない。

 初めてこの方法を思いついたとき,私はIISの使い方として間違っている感じがした。結局のところ,この機能はWebサイトのルートページだけのために用意されているように感じられるのに,私はすべてのページでこれを使おうとしている。何か見えないペナルティはあるだろうか。この方法を採用したあと,たとえば,http://bestpayroll.com/reportを要求したとして,Webサーバのログを見ると,次のようなエントリが含まれるようになる。

23:49:55 192.168.27.1 GET /report/ 302
23:49:55 192.168.27.1 GET /report/Default.aspx 200
 1つの要求のためにログ内に2つのエントリが作られてしまうのは問題である。いったい何が起きたのだろうか。Webサーバに最初の要求が届いたとき,サーバは302というエラーをクライアントに返している。300番台のステータスメッセージは,Redirectionクラスに属している。つまり,これらのメッセージは,リソースが移動されており,ブラウザを新しい位置にリダイレクトすることをブラウザに告げるものである。302というステータスコードは,ブラウザに対し,リソースが一時的に移動されていることを知らせる。そして,サーバは302ステータスメッセージとともに,新しいURLを送る。詳細は,HTTPペイロードを見れば分かる。

HTTP/1.1 302 Object Moved
Location: http://192.168.27.130/bestpayroll/report/
Server: Microsoft-IIS/5.1
Content-Type: text/html
Content-Length: 164
すると,ブラウザは,末尾に/を追加した新しいURLを試す。Webサーバは,これに対してデフォルトドキュメントを応答する。すべてのページについてこのようなリダイレクトを行なうことは避けたほうがよいだろう。これよりもよい方法があることは間違いない。

ASP.NETでHTTPフィルタを使う
 送られてくるURLを早い段階で横取りし,書き換える方法のほうがよい。ASP.NETが登場する前,HTTP呼び出しを横取りするための手段は,ISAPI以外にはなかった。ASP.NETは,IHttpModuleインターフェイスを中心とする新しいAPIセットを導入した。このインターフェイスを実装するオブジェクトは,HTTP要求が処理されるときに通知を送るようにWebアプリケーションに依頼することができる。

 仕組みを説明しよう。プログラマが,IHttpModuleインターフェイスを実装するクラスを作る。IHttpModuleインターフェイスは,2つのメソッドを定義するだけである。

interface IHTTPModule {
    void Init(HTTPApplication context);
    void Dispose();
}
Initメソッドは1度だけ,アプリケーションが初期化されるときに呼び出される。アプリケーションの初期化が終わると,HTTPApplicationを引数としてInitメソッドが呼び出される。Initは,1つ以上のイベントにハンドラを結び付けることになっている。Disposeメソッドは,アプリケーション終了時に1度だけ呼び出される。アプリケーションが終了し,再初期化されるのは,アプリケーションやIISが再起動されたときだけではなく,Web.config設定ファイルや依存アセンブリが何らかの形で変更されたときも含まれることを忘れてはならない。

 Figure 1のコードは,最小限のHTTPモジュールのコードを示したものである。このコードは,要求が初めて届いたときに呼び出され,Windowsシステムロギング機能を使ってイベントのログを残し,制御を返す。

 Webアプリケーションでフィルタをアクティブにするには,ファイルをコンパイルしてアセンブリを作り,アプリケーションからアセンブリにアクセスできるようにしなければならない。次に,Web.configファイルを書き換え,モジュールを追加する必要がある。

<configuration>
    <system.web>
        //vt
        <httpModules>
            <add type="Panos.Test.HTTPModule,PanosTest" name="test" />
        </httpModules>
        //vt
    </system.web>
...
</configuration>
 ASP.NETランタイムは,BeginRequestメッセージのほか,アプリケーションオブジェクトについてのその他のイベントも提供する。ASP.NET HTTPフィルタは,これらすべてのイベントを対象としてハンドラをセットアップできる。Figure 2は,要求処理の進行に伴い,ランタイムが発生させるすべてのイベントとその順番をまとめたものである。

イベント(発生順)
Figure2:イベント(発生順)

 マルチモジュールの場合,WebコンフィギュレーションファイルのHttpModulesに書かれている順番で,同じイベントが生成される。たとえば,AとBの2つのモジュールを持つアプリケーションがあり,それらがともにBeginRequestとAuthenticateRequestの2つのイベントを処理し,そしてコンフィギュレーションファイルのHttpModulesセクションのなかでモジュールAが先に書かれている場合,次の順番で4つのイベントが処理される。

  • モジュールAがBeginRequestを処理する
  • モジュールBがBeginRequestを処理する
  • モジュールAがAuthenticateRequestを処理する
  • モジュールBがAuthenticateRequestを処理する
ASP.NET HTTPモジュールを使ってURLを書き換える
 ASP.NETのHTTPフィルタリングについて研究を始めたとき,ASP.NET HTTPフィルタリングを使ってURLの書き換え問題を解決するための方法はきっとあるはずだと私は思っていた。しかし,一般的な意味では,これは可能ではないことが分かった。これは,実際にはCLR(Common Language Runtime)やASP.NETの限界ではなく,IIS 5.0の限界に理由がある。

 IIS 5.0は,それまでのバージョンと同様に,拡張機能を判断するためにファイル拡張子を使っている。Figure 3を参照していただきたい。IISメタベースのこの画面は,特定の拡張子とランタイムシステムの対応関係を示している。対応関係をじっくり見ると,すべてのASPエクステンションがasp.dllに結び付けられているのに対し,ASP.NETエクステンションはaspnet_isapi.dllによって処理されることが分かる。実際,.fooという拡張子を持つファイルを処理するASP.NETハンドラを書いたら,.fooファイルとそのハンドラが対応付けられるようにWeb.configファイルを書き換えるとともに,拡張子.fooのファイルをASP.NETに処理させるようにIISを設定しなければならない。

IISメタベース
Figure3:IISメタベース

 IISは,/bestpayroll/msdnmag/reportのように拡張子を持たないパスをどのように処理するのだろうか。IISは,仮想パスが既存の物理ディレクトリにマッピングされているかどうかをチェックする。マッピングされていれば,デフォルトドキュメントリストを参照し,一致するものを探す。そのようなものがあれば,先ほど説明したように,クライアントをそのファイルにリダイレクトする。そうでなければ,HTTP 404(ファイルが見つからない)エラーを返す。

 以上の知識を身に付けたWebプログラマは,ビジネスアナリストのところに戻り,プロジェクトを完成させるためには,/msdnmag/reportの代わりに使う/msdnmag/report.bestpayrollのようなファイル拡張子が必要だということを説明することができる。

 この考え方が受け入れられれば,ASP.NETは,IHttpHandlerインターフェイスを使ってプログラマを助けられる。このインターフェイスを実装するオブジェクトは,HTTP要求を直接処理できる。実際,Webフォームは,コードビハインドクラスを継承するクラスにコンパイルされる。コードビハインドクラスは,System.Web.UI.Pageクラスを継承しており,つまりこのクラスはIHttpHandlerインターフェイスを実装している。

 IHttpHandlerの実装で重要なメソッドは,ProcessRequestである。このメソッドは,HttpContext型の引数を取る。この引数は,Request,Responseなど,役に立つすべてのコンテキストオブジェクトへのアクセスを提供する。私の場合,HTTPハンドラは,仮想パスを走査して要求を別のページにリダイレクトするために使いたい。クライアントへの往復を避けるためにサーバサイドでリダイレクトを処理したいのである。これを実現するために,Server.Transferメソッドを使うことにする。このメソッドは,ASP版とよく似ている。

 これ以外に触れておかなければならないことは,HTTPハンドラクラスがASP.NETフォームとの間で社名をやり取りする仕組みだけである。HTTPハンドラは,URLの仮想パスを走査して社名を探す。私のサンプルでは,HttpContext.Itemsコレクションを使っている。ASP.NETページは,<%=Context.Items["company"]%>式を使って社名を集めることができる。Figure 4は,リダイレクトハンドラを実装するコードを示したものである。

 クラスを実装し,アセンブリにパッケージングしたら,要求が届くたびにこのハンドラを起動するようにWebサイトを設定する必要がある。設定すべき場所は2カ所ある。Web.configファイルには,.bestPayrollという拡張子を含むURLをハンドラに対応付けるエントリを追加する。私のサンプルの場合には,次のようにすればよい。

<configuration>
  <system.web>
    ...
    <httpHandlers>
      ...
      <add verb="*" path="*.bestpayroll"
        type="Panos.Test.HttpHandler,Panos.Test" />
    </httpHandlers>
  <.system.web>
</configuration>
 次に,.bastpayrollという拡張子を持つファイルに対する要求をASP.NETランタイムに転送するようにIISを設定しなければならない。これは,プログラムから設定することもできるし,IIS管理UI(Figure 5)から設定することもできる。現在のところ,.NETランタイムDLLは,C:\WINDOWS\Microsoft.NET\Framework\v1.0.3705\aspnet_isapi.dllである。ASP.NETランタイムは,.NET Frameworkのバージョンごとにまちまちになっている。正しいものを見つけるための最も安全な方法は,.aspxファイルに対応付けられているDLLを探すというものである。おそらく,.NETを今よりも強く意識することになるはずの将来のバージョンのIISでは,この最後のステップはまったく実行せずに済ませられるようになるだろう。

拡張子の対応付け
Figure5:拡張子の対応付け

 HTTPハンドラの実装,設定方法についてより詳しく知りたい読者は,http://support/microsoft.com/default.aspx?scid=kb;en-us;0308001を参照していただきたい。

ISAPIフィルタを使ったURLの書き換え
 ここまで読んだ読者は,前節のようにファイル拡張子を使うという制限なしで任意のURLをASP,ASPXファイルにマッピングする一般的な解決方法が欲しいと思われたことだろう。私の知る限り,これを実現するためには,.NET Frameworkの外に出て,古いISAPIフィルタを使わなければならない。ご存知のようにISAPIフィルタとは,IISネイティブのHTTPハンドラで,ASP.NETのフレームワークコードが実行される前の非常に早い段階で実行のチャンスを与えられる。

 概念的には,ISAPIフィルタは,先ほど触れたASP.NETのHTTPモジュールと非常に近い。大きな違いは,ISAPIフィルタはCかC++で書かなければならないということである。ISAPIフィルタについて触れている文献は無数にあるので,ここではごく簡単に触れるだけに留めておくことにする(詳しくは,MIND 日本語版 No.1の記事「ISAPI」,原著テキストはhttp://www.microsoft.com/Mind/0396/isapi/isapi.htm,MIND 米国版の記事」「Tips and Tricks for ISAPI Programmingを参照)。

 実践的に言えば,ISAPIフィルタとは,次の2つのエントリポイントを提供するWindows DLLである。

DWORD WINAPI HttpFilterProc(
  PHTTP_FILTER_CONTEXT pfc,
  DWORD notificationType,
  LPVOID pvNotification
);
    
BOOL WINAPI GetFilterVersion(
  PHTTP_FILTER_VERSION pVer
);
IISランタイムは,フィルタを初期化するときに,GetFilterVersionメソッドを呼び出す。フィルタは,この機会を利用して,IISに処理しようと思っているイベントを通知しなければならない。すると,HTTP要求が届いたときに,IISはフィルタが通知を求めたすべてのイベントについて,HttpFilterProcメソッドを呼び出す。少しでも作業を楽にしたければ,MFCのCHttpFilterクラスを使えばよい。このクラスは,生のC APIにラップをかけ,プログラマが実装した1つ以上の仮想メソッドにイベントをルーティングする。以下の議論は,このMFC提供のラッパを使っているという前提のもとで進める(MSJ 日本語版 No.44の記事「MFC4.1の新しいISAPIクラスによる手軽な対話的Webアプリケーションの作成」,原著テキストはhttp://www.microsoft.com/msj/archive/s2d4.htmを参照)。

 Figure 6は,URLを書き換え,擬似ヘッダーに仮想パスの一部を格納するC++コードである。URLを書き換えるためにキャッチしなければならないイベントは,OnPreprocHeadersだ。ここでの手品の種は,オリジナルのURLを読み出すために特別なヘッダーURLが使えることである。URLを書き換えたら,SetHeader呼び出しを使って,特殊ヘッダーURLをセットし,IISがISAPIハンドラを使うようにする。Figure 6のコードは,/bestpayroll/msdnmag/report2のようなURLを/bestpayroll/report2.aspxに書き換える。社名が取り除かれ,.aspxという拡張子が追加されていることに注意していただきたい。醜いコードの大半は,社名を抜き出し,URLのその他の部分を組み合わせて要求を作っている。

 残された仕事は,ASP.NETフォームにオリジナルのURLか社名を知らせることである。ISAPIフィルタからASPやASP.NETとやり取りするためのクリーンな方法はない。可能な方法は,新ヘッダーの追加である。次のように,Request.ServerVariablesコレクションを使えば,ASP.NETからcompanynameというヘッダーを読み出せる。

<form id="test" method="post" runat="server">
    The company is
    <b><%=Request.ServerVariables["HTTP_COMPANYNAME"]%></b>
</form>
ここでは,C++コードにURL書き換えロジックをハードコードしたが,よりリアルなシナリオでは,初期化中にフィルタが走査する設定ファイルに書き換えロジックを収めるべきだろう。

まとめ
 本稿では,URLの書き換えという単純な問題を使って,HTTP呼び出しをフィルタリングするための異なる方法を説明した。ASP.NETは,CLRを利用してHTTP呼び出しをフィルタリングする新しい方法を追加した。ほとんどの場合は,これでうまく機能する。しかし,ASP,ASP.NETランタイムが実行されるよりも前の早い段階で呼び出しをフィルタリングするためには,依然としてC++プログラミングが必要になる。私は,IISの将来のバージョンでは,仮想パスのプレフィックスに基づいて要求を処理するISAPIエクステンションを指定できるようになるのではないかと期待している。


Panos Kougiouris:Panos Kougiourisは,Windows DNA,.NETなどのテクノロジーを使って数種類のインターネットアプリケーションを開発した実績を持っている。連絡先は,panos@acm.org。



グレープシティ(株)社長インタビュー
 back to top Last Updated: 2004/07/23     
Copyright (C) 2001 ASCII Corporation. All rights reserved.
プライバシーポリシー