IIS 7.0
統合 ASP.NET パイプラインでアプリケーションを拡張する
Mike Volodarsky
この記事は、IIS 7.0 FastCGI コンポーネントのプレリリース版を基にしています。ここに記載されているすべての情報は、変更される場合があります。
この記事の内容 : :
- ASP.NET の Integrated モード
- ユーザー認証の追加
- 検索エンジン フレンドリーな URL の有効化
- 出力キャッシングによるパフォーマンスのアップグレード
|
この記事は次のテクノロジを使用しています:
IIS 7.0、.NET Framework
|

コンテンツ
ほぼ 1 年前、筆者は MSDN
® マガジンに IIS 7.0 の概要を執筆しました (
msdn.microsoft.com/msdnmag/issues/07/03/IIS7 の「IIS 7.0: Windows Vista 用 Web サーバーの新機能」を参照してください)。それは、Windows Vista
® と共に IIS 7.0 がリリースされる数か月前でした。それ以降、ユーザーには、新しい IIS 7.0 のコンポーネント化された拡張可能なアーキテクチャとその他の改善を直接経験するチャンスがありました。
Windows Vista が出荷されてから、私たちは IIS 7.0 を Windows Server® 2008 の信頼できる安全な Web サーバーとし、その安定性、パフォーマンス、およびホスティング環境のサポートに磨きをかけることに力を注いできました。また、柔軟性のある Web アプリケーション プラットフォームであることが IIS 7.0 にとってどのような意味を持つかを綿密に検討しました。ASP や ASP.NET のような Microsoft アプリケーション フレームワークの優れたプラットフォームとすることだけでなく、現在使用されている他のさまざまなアプリケーション フレームワークに対しても優れたプラットフォームにすることを望んでいました。これを促進するために、FastCGI のサポートを追加しました。FastCGI は、PHP、Ruby on Rails、Perl などのアプリケーション フレームワークを IIS にホストできるようにする公開 Web サーバー規格です。また、PHP の作成者である Zend Technologies と協力して、Windows® と IIS に堅牢でパフォーマンスの高い PHP 実装をもたらしました。
IIS 7.0 は、普及しているアプリケーション フレームワークの実稼働サポートを提供するだけではありません。新しい Microsoft® .NET Framework 拡張モデルにより、IIS 7.0 は ASP.NET の Integrated モードの能力を活用して、任意のフレームワークで開発されたアプリケーションに重要な機能を追加できます。これにより、ほとんどの場合はコードを 1 行も触らずに、アクセス制御や新しい URL スキームなどの優れた機能を追加したり、パフォーマンスを大幅に向上させたりすることができます。
この記事では、IIS 7.0 ASP.NET 統合機能を深く掘り下げて、ASP.NET を考慮して開発されていないアプリケーションを拡張します。既存の ASP.NET 機能でアプリケーションを補強する方法と、IIS レベルの ASP.NET 拡張機能を利用する新機能を開発してアプリケーションに追加する方法を示します。
使用するアプリケーションは、Qdig (
qdig.sourceforge.net) という普及型の PHP イメージ ギャラリー アプリケーションです。PHP コードを 1 行も触らずに、いくつかの便利な新機能をイメージ ギャラリーに追加する方法を示します。最初に、ASP.NET メンバシップを使用してギャラリーをパスワードで保護し、認証機能を作成します。また、見にくいパラメータ化クエリ文字列の URL ではなく検索エンジン フレンドリーな URL を使用するようにアップグレードします。最後に、ASP.NET 出力キャッシュを使用してアプリケーションのパフォーマンスを大幅に改善します。
ただし、まずは IIS での新しい PHP サポートに隠された歴史を見てみましょう。この PHP サポートは、PHP のようなアプリケーション フレームワークで IIS 7.0 機能の完全なセットを利用できるようにするための中核です。
IIS と PHP
IIS は常に PHP をサポートしてきましたが、多くの現実の PHP アプリケーションは、実稼働環境にホストされていませんでした。それは、PHP アプリケーションを実行するために IIS が提供していた Common Gateway Interface (CGI) プロトコルの使用または PHP ISAPI 拡張機能の使用という 2 つの点に制限があるためです。
CGI では要求ごとに別々のプロセスが必要であるため、CGI を使用してホストされているアプリケーションは、Windows 上でパフォーマンスが悪くなります。反対に、IIS の高パフォーマンス マルチスレッド ISAPI インターフェイスを使用している PHP アプリケーションは、一部の普及している PHP 拡張機能にスレッド セーフが欠落しているため不安定になる場合があります。
これらの問題を解決しようとして、IIS チームは FastCGI コンポーネントを開発しました。公開 FastCGI プロトコルによって、PHP、および単一スレッド環境を必要とする他の多くのアプリケーション フレームワーク (Ruby on Rails、Perl、Python など) は、IIS での実行の信頼性が高くなります。標準の CGI 実装とは異なり、FastCGI では、それぞれが一度に複数の要求を処理しないワーカー プロセスのプールを維持することでプロセスを再利用できるため、パフォーマンスが大幅に向上します。FastCGI は、コミュニティ中心の開発およびテスト モデルの恩恵も受けます。
同時に、Zend Technologies は、Windows 上の PHP スクリプティング エンジンとコア拡張機能の全般的なパフォーマンスと安定性の向上に取り組み、多数のパフォーマンス改善と数十個の Windows 固有バグの解決を行いました。
ここで説明しているように、
php.net/downloads では、IIS FastCGI プラットフォーム用に最適化された高速非スレッド セーフ ビルドと共に、Windows ホスティング用に最適化された PHP の第 3 リリースである PHP 5.2.3 が提供されています。IIS FastCGI サポートは、Windows Server 2008 のベータ 3 リリース以降に組み込まれており、Windows Vista、Windows XP、および Windows Server 2003 の Go-Live リリース用に独立した Tech Preview 版ダウンロードとして入手できます (
iis.net/fastcgi)。Windows Vista SP1 の出荷時には、このコンポーネントはインストール パッケージの一部としても入手できるようになります。近い将来には、Windows XP および Windows Server 2003 用の最終バージョンもリリースされます。IIS FastCGI を実行する場合のオプションについては、
mvolo.com/blogs/serverside/archive/2007/10/09/IIS-FastCGI-and-PHP_3A00_-What-you-absolutely-need-to-know-to-host-PHP-applications-on-IIS-6-and-IIS-7.aspx で今すぐ参照できます。
アプリケーションを設定する
FastCGI を搭載した IIS 7.0 によって、PHP アプリケーションの設定がかなり単純になります。まず、Web サイトを作成し、ローカル マシンから http://myphpgallery としてアクセスできるように myphpgallery ホスト ヘッダー バインドを追加しました (myphpgallery ホスト名を %windir%\system32\drivers\etc\hosts に追加して、マシンがその場所を認識できるようにもしました)。
次に、Qdig の最新バージョンを
qdig.sourceforge.net からダウンロードし、Web サイトのルートに解凍しました。ギャラリーのテストを準備するために、多数のイメージを Web サイトのルートにドロップしました (サブディレクトリを作成し、イメージをそこにドロップすることもできます)。ここでは、Windows Vista に付属のサンプル イメージをいくつか使用しました。
Windows 用の最新の非スレッド セーフ PHP ビルド (入稿時点では PHP 5.2.3) を
php.net/downloads からダウンロードし、C:\php に解凍しました。Qdig に必要ないくつかの微調整を php.ini に対して行う必要がありましたが、この時点ではかなりうまく行っていました。
- php-recommended.ini を php.ini に名前変更する
- "register_long_arrays=On" を設定する
- "extension=php_gd2.dll" で GD 拡張機能を有効にする
- "extension_dir=./ext" で正しい拡張パスを設定する
IIS の観点からは、PHP アプリケーションを実行するにはいくつかの手順のみで済みます。まず、Windows Vista の場合は
go.microsoft.com/fwlink/?LinkId=104195、Windows Server 2008 および Windows Vista SP1 の場合は
go.microsoft.com/fwlink/?LinkId=104196 の記事に従って、PHP/FastCGI ハンドラ マッピングを PHP-CGI.EXE に追加します。次に、index.php を既定のドキュメントとして追加します。
最後に、Qdig はサムネイルをその場で生成するため、IIS ワーカー プロセスがアプリケーションの qdig-files サブディレクトリに書き込めるように、このサブディレクトリへの書き込みアクセス権を IIS_IUSRS グループに付与する必要がありました。
これで準備完了です。この時点で、http://myphpgallery にアクセスすると、図 1 に示すようなイメージが表示されます。
図 1 Qdig ギャラリー (画像を拡大するには、ここをクリックします)
ギャラリーを保護する
Qdig は、Web を介してイメージ コレクションを参照するという 1 つの処理を正常に行うことに重点を置いた単純なイメージ ギャラリーです。このため、Web アプリケーションで必要とされることの多い複雑な機能の一部がありません。このような機能の 1 つは、Web アプリケーションの一部またはすべてへのアクセスを制限できるようにするアクセス制御です。
あいにく、PHP でアクセス制御を実装するには、資格情報ストアと cookie ベースの認証を新たに実装する必要があり、この作業を正しく行うのはかなり困難です。対照的に、ASP.NET は、メンバシップ サービスと組み込み資格情報ストア プロバイダのセット、フォーム認証モジュール、および作成済みのログイン コントロールのセットを備えた完全なソリューションを提供することで、アクセス制御を簡単にします。
以前のバージョンの IIS では、このことは Qdig ギャラリーにとって大きな意味を持ちません。ASP.NET アプリケーションではないからです。ただし、IIS 7.0 では ASP.NET の Integrated モード エンジンがこのシナリオ専用にデザインされており、他のアプリケーション フレームワークを含め、任意のコンテンツで ASP.NET 機能を使用できます。ASP.NET の Integrated モードの機能を使用して、Qdig ギャラリーは、ネイティブ ASP.NET アプリケーションと同じように ASP.NET メンバシップ、フォーム認証、およびログイン コントロールを利用できます。実際に、フォーム認証を使用する既存の ASP.NET アプリケーションが既にある場合は、単純に Qdig ギャラリーにドロップし、わずか数ステップで他のアプリケーションと同じユーザー セキュリティをもたらすことができます。その結果、サイト全体で一貫したユーザー セキュリティが実現されます。
ギャラリーのフォーム認証ソリューションをゼロから実装するために、まず、ユーザー資格情報を格納するためのメンバシップ資格情報ストア プロバイダを選択する必要がありました。ASP.NET 2.0 には 2 つの組み込みプロバイダが付属しています。一方は SQL ServerTM 用で、もう一方は Active Directory® 用です。
SQL Server プロバイダは、多くのアプリケーションにとって優れたオプションである SQL Server 2005 Express Edition と共に使用できます。SQL Server 2005 Express Edition をメンバシップ プロバイダと共に使用する場合の最も優れた点は、初回使用時にデータベース テーブルを自動的に作成し、アプリケーションの App_Data サブディレクトリに配置する機能にあります。これには外部データベース配置は不要であり、アプリケーション ファイルで Xcopy できる資格情報データベースが生成されます。
SQL Server メンバシップ プロバイダは、既定では、ローカル SqlExpress インスタンスを使用する接続文字列を使用して、アプリケーションの App_Data ディレクトリ内にある aspnetdb.mdf データベースに接続するように構成されています。これを機能させるために、単純に次の 3 つの手順を実行しました。
- SQL Server 2005 Express Edition をダウンロードしてインストールし、名前付き SqlExpress インスタンス (既定) として実行しました (microsoft.com/sql/editions/express からダウンロードできます)。
- App_Data サブディレクトリをアプリケーションのルートに作成し、IIS_IUSRS から書き込み可能にしました。
- IIS マネージャを開き、アプリケーション用にいくつかのユーザーを作成しました。この操作を行うには、左側のツリー ビューで Web サイトを選択し、[.NET ユーザー] 機能アイコンをクリックします (図 2 を参照)。SQL Server Express Edition では一度に 1 つのユーザー ID しかデータベースにアクセスできないため、終了したら必ずツールを閉じてください。
図 2 IIS マネージャでユーザーを構成する (画像を拡大するには、ここをクリックします)
IIS マネージャと ASP.NET メンバシップ インフラストラクチャ間の統合に注意してください。他の IIS 認証およびアクセス制御機能を構成する管理ツールから、ユーザーとロールの両方を直接管理できます。実際に、最初のユーザーを作成するときに、アプリケーションにメンバシップ データベースが存在しない場合は、IIS マネージャによってメンバシップ データベースが自動的に作成されます。
フォーム認証
メンバシップの準備ができたところで、アプリケーションに対してフォーム認証を有効にする必要があります。また、認証サービスを実装するフォーム認証モジュールを、ASP.NET 要求だけでなくアプリケーションのすべての要求に対して実行できるようにする必要があります (これは、下位互換性を保つための既定の動作です)。次のように、この処理はすべて Web サイトの web.config ファイルで直接実行できます。
<configuration>
<system.webServer>
<modules>
<remove name="FormsAuthentication" />
<add name="FormsAuthentication"
type="System.Web.Security.FormsAuthenticationModule" />
</modules>
</system.webServer>
<system.web>
<authentication mode="Forms" />
</system.web>
</configuration>
FormsAuthentication モジュールはサーバー レベルで既に宣言されていますが、これを削除し、再び宣言して、既定で managedHandler に設定されている preCondition 属性をクリアする必要があります。この設定では、モジュールが強制的にマネージ (ASP.NET) ハンドラへの要求に対してのみ実行されます。フォーム認証で PHP アプリケーションも保護する必要があるため、この設定を削除する必要があります。
この時点で、フォーム認証を使用するようにアプリケーションが構成されています。ただし、コンテンツが実際に認証済みユーザーを必要とすることは指定しませんでした。匿名認証を有効なままにして、サイトへの匿名アクセスを許可したため、認証は使用されません。実際には、ログイン ページへの匿名アクセスを許可し、残りのコンテンツの認証を要求することのみ必要です。宣言型の認証ルールを構成することで、この処理を行うことができます。web.config ファイル内の完成した構成を図 3 に示します。

Figure 3 Web.config
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<!-- The PHP handler mapping -->
<handlers>
<add name="PHP via FastCGI" path="*.php" verb="*" modules="FastCgiModule" scriptProcessor="
c:\php\php-cgi.exe" resourceType="Unspecified" />
</handlers>
<!-- Add index.php as a default document -->
<defaultDocument>
<files>
<add value="index.php" />
</files>
</defaultDocument>
<security>
<!-- Deny access to anonymous users -->
<authorization>
<add accessType="Deny" users="?" />
</authorization>
</security>
<!-- Let the FormsAuthentication module run for all requests -->
<modules>
<remove name="FormsAuthentication" />
<add name="FormsAuthentication" type=
"System.Web.Security.FormsAuthenticationModule" />
</modules>
</system.webServer>
<system.web>
<!-- Turn on Forms Authentication for this application -->
<authentication mode="Forms" />
</system.web>
</configuration>
この構成では、PHP ギャラリーへのアクセスは、認証済みユーザーに対してのみ許可されます。認証されていないユーザーは、URL 認証機能によって拒否され、フォーム認証によってログイン ページにリダイレクトされます。ログイン ページで、ユーザーはメンバシップ サービスと SQL Server 資格情報データベースを使用してログインできます。
ジグソー パズルの最後のピースは、ログイン ページです。このページは、ログイン Web フォームを提供し、メンバシップ サービスでユーザーの資格情報を検証し、ユーザーに暗号化形式の認証ログイン チケットを発行することで、すべてを結び付けます。この時点で腕まくりをしてコーディングを始めようとしているなら、ちょっと待ってください。このログイン ページはかなりの部分が既に用意されています。行う必要があるのは、login.aspx という単純なページをアプリケーションのルートに作成することだけです (ログイン ページへのパスは、<forms> 構成セクションでオーバーライドできます)。この新規作成されたページでは、単純な Login コントロールが使用されます。
<html>
<head>
<title>Login to my blog</title>
</head>
<body>
<form runat="server">
<asp:Login runat="server" />
</form>
</body>
</html>
Login コントロールは、ログイン フォームを自動的に表示し、送信後にユーザー資格情報を検証し、フォーム認証チケットを発行します。このチケットは、非アクティブになるかユーザーがブラウザ ウィンドウを閉じたために失効するまで、Web サイト上の残りの時間についてユーザーを自動的に認証するために使用されます。
図 4 に、ログイン ページの整理したバージョンを示します。より便利なユーザー エクスペリエンスを提供するために、他のログイン関連コントロールをいくつか使用しています。LoginView コントロールでは、匿名ユーザーのビューか認証済みユーザーのビューかを選択できます。LoginName コントロールは認証済みユーザーの名前を提供し、LoginStatus コントロールは認証済みユーザーのログアウト リンクを提供します。先に進んで多数の書式設定プロパティの 1 つを設定するか、カスタム CSS でテーマ設定またはスキニングすることにより、クライアントに対するログイン コントロールの表示方法を指定できます。

Figure 4 手の込んだログイン ページ
<html>
<head>
<title>Login to view the gallery</title>
</head>
<body>
<form runat="server">
<asp:LoginView runat="server">
<AnonymousTemplate>
Please log in to proceed.
<br /><br />
<asp:Login DestinationPageUrl="/" runat="server" />
</AnonymousTemplate>
<LoggedInTemplate>
Welcome <asp:LoginName runat="server" />!
<br /><br />
<asp:LoginStatus runat="server" />
</LoggedInTemplate>
</asp:LoginView>
</form>
</body>
</html>
これで準備完了です。ギャラリーにアクセスすると、図 5 に示すログイン ページに自動的にリダイレクトされます。ログインするには、前に作成したいずれかのユーザーのユーザー資格情報を入力する必要があり、それで作業を開始できます。
図 5 完成したログイン ページ (画像を拡大するには、ここをクリックします)
検索エンジン フレンドリーな URL
既定では、Qdig イメージ ギャラリーでは見にくいが機能するクエリ文字列 URL を使用してイメージ ギャラリーをナビゲートします。次に、筆者のギャラリーの Qdig URL の例を示します。
http://myphpgallery/index.php?Qwd=./Mike&Qif=Flower.jpg
特におもしろくも覚えやすくもありません。さらに重要なことに、検索エンジンがギャラリーのコンテンツを正しくインデックス付けすることが難しくなります。
ここで行う必要があるのは、Qdig が検索エンジン フレンドリーな URL を使用するようにすることです。検索エンジン フレンドリーな URL は、ユーザーの参照操作をはるかに直観的にし、検索エンジンが Web サイトを適切にインデックス付けする際にはるかに有効であるため、最近の Web 2.0 アプリケーションで流行しています。たとえば、前に示した URL のフレンドリー URL バージョンを次に示します。
http://myphpgallery/index.php/Mike/Flower.jpg
この URL では、サブギャラリーとイメージ名が URL パスに挿入されているため、直観的になっています。また、重要なキーワードが URL の絶対パスに配置されているため、検索エンジンがインデックスを付けやすくなっています。
興味深いことに、サーバーはこの URL を既定で index.php に正しくルーティングし、残りの "/Mike/Flower.jpg" を PATH INFO セグメントとして認識します。ただし、ここで問題になるのは、この URL が Qdig アプリケーションのスクリプトである index.php で期待される URL 形式に従っていないことです。URL で index.php スクリプトを呼び出すことができても、Qdig は必要なクエリ文字列パラメータを見つけられず、正しく動作する方法を知ることができません。
さいわい、今回も ASP.NET の Integrated パイプラインで救済できます。受信した URL をフレンドリーな形式から Qdig ネイティブ URL にその場でリライトできる小さな .NET モジュールを作成して、適切なギャラリー ビューがクライアントに返されるようにすることができます。ASP.NET の Integrated パイプラインのおかげで、このモジュールを ASP.NET コンテンツに対する要求の場合と同じ方法で PHP 要求に対して実行できます。
これを行うために、ASP.NET IHttpModule パターンを使用してマネージ モジュールを実装します。IIS 7.0 でのこのようなモジュールの構築の詳細については、mvolo.com/blogs/serverside/archive/2007/08/15/Developing-IIS7-web-server-features-with-the-.NET-framework.aspx にある「.NET Framework での IIS7 モジュールおよびハンドラの開発」を参照してください。
モジュールでは、次の処理を行います。
- 要求処理の BeginRequest ステージをサブスクライブします。
- フレンドリー形式の URL を使用した要求をインターセプトします。
- URL からサブギャラリーのパスとイメージ ファイル名を抽出します。
- Qdig index.php への要求をリライトし、サブギャラリー パスとイメージ ファイル名を Qdig で期待されるクエリ文字列パラメータで渡します。
このモジュールの作成は、実際にはかなり単純でした。最初に、System.Web.IHttpModule インターフェイスを実装する C# クラスを作成し、Init メソッド内のイベント ハンドラを BeginRequest イベントに関連付ける必要がありました。
public class QdigSEFModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.BeginRequest += new EventHandler(OnBeginRequest);
}
...
}
次に、図 6 に示すように、OnBeginRequest メソッドのリライト機能を実装します。

Figure 6 URL をリライトする
public void OnBeginRequest(Object source, EventArgs e)
{
HttpApplication app = (HttpApplication)source;
HttpContext context = app.Context;
// Extract the gallery path and image filename from the url
// code omitted for clarity ...
// Build the rewritten url
String rewrittenUrl = String.Format("~/index.php?Qwd=.{0}&Qif={1}",
galleryPath,
imageFileName);
String originalQueryString =
context.Request.ServerVariables["QUERY_STRING"];
if (!String.IsNullOrEmpty(originalQueryString))
{
rewrittenUrl += "&" + originalQueryString;
}
// Rewrite the url
context.Server.TransferRequest(rewrittenUrl);
}
.NET Framework 3.5 の新しい ASP.NET API である HttpServerUtility.TransferRequest は、IIS 7.0 ASP.NET Integrated パイプラインでの適切なリライトを可能にするために使用しています。この API は、現在の要求に対して要求処理を強制的に停止し、ターゲット URL に対する新しい子要求を実行します。これにより、マネージ モジュールは、転送先のコンテンツ タイプに関係なく、要求処理を別の URL に完全に転送できます。このモジュールの完全なソース コードは、この号のダウンロードに含まれています。
モジュールを作成した後で、アプリケーションに配置する必要があります。.NET アセンブリへのモジュールのコンパイルや、アプリケーションの BIN ディレクトリへの配置など、その方法はいくつかあります。ただし、最も簡単なオプションを追求します。モジュールのソース コードを単純にコピーし、それを QdigSEFRewriter.cs としてアプリケーションの App_Code サブディレクトリに保存します。ASP.NET コンパイル システムによって、アプリケーションの起動時にコードが自動的にコンパイルされ、ASP.NET ランタイムで使用可能になります。
最後の手順では、アプリケーションの web.config ファイルの <modules> 構成セクションに登録することで、モジュールをアプリケーションで実行できるようにします。このセクションには、フォーム認証などのいくつかの組み込み ASP.NET モジュールが既に既定で登録されています。アクセス制御用に作成された web.config ファイルの上に、次のような新しいモジュールを追加できます。
<system.webServer>
<modules>
<remove name="FormsAuthentication" />
<add name="FormsAuthentication" type=
"System.Web.Security.FormsAuthenticationModule" />
<!-- Add the SEF url rewriting module -->
<add name="QdigSEFUrlsModule" type="QdigModules.QdigSEFModule"/>
</modules>
</system.webServer>
この時点で、Qdig のネイティブ クエリ文字列 URL の代わりにフレンドリーな URL を使用して、Qdig ギャラリーへのアクセスを開始できます。ここで、ギャラリーの Mike サブディレクトリ内の Flower.jpg ファイルにアクセスするには、単純に次のようにファイルを要求します。
http://myphpgallery/Mike/Flower.jpg/show
フレンドリーな URL をイメージ ファイルへの直接リンクと区別し、リライトする必要のない URL がモジュールによってリライトされることを防ぐために、フレンドリーな URL には /show 接尾辞を使用することを選択しました。当然、自分のニーズに適した方法でリライト スキームを変更できます。
フレンドリーなハイパーリンクを生成する
残念ながら、まだ完全には終わっていません。ギャラリーはフレンドリーな URL でアクセスできるようになりましたが、ギャラリー ページ自体にはまだ古い形式のハイパーリンクが含まれています。
<a href="http://index.php?Qwd=./Mike&Qif=Arctica.jpg&Qiv=thumbs&
Qis=M" title="First Image"> ... </a>
ユーザーがこれらのリンクをクリックすると、見苦しいクエリ文字列 URL が 表示され、コンテンツをインデックス付けしている検索エンジンは、ページ本文のリンクに親ページのフレンドリー URL を関連付けることができません。
前の手順で有効にしたのと同じフレンドリー URL を使用して Qdig がハイパーリンクを生成するようにすることは、本当に簡単です。ただし、これを行うには、PHP スクリプトをリライトして別のハイパーリンクを生成する必要があります。この処理は行いたくありませんでした。
ASP.NET の応答フィルタ機能を使用すれば、この処理を行う必要がないことがわかりました。Qdig 応答をその場でフィルタ処理する別のマネージ モジュールを作成し、古い形式のハイパーリンクを新しいフレンドリー形式のリンクで置換できます。
これで、モジュールは次の処理を行うようになります。
- (ハンドラの実行前に) 要求処理の PreRequestHandlerExecute ステージをサブスクライブします。
- URL が Qdig index.php スクリプトに対するものかどうかをチェックします。
- 応答に、正規表現を使用してフレンドリーなハイパーリンクの出現をすべて置換する応答フィルタ ストリームを設定します。
モジュール クラスが行う処理は、フィルタ ストリームを選択的に定義することだけなので、モジュール クラスはかなり単純です (図 7 を参照)。実際の処理は、QdigSEFFilter クラスで行われます。このクラスは、Qdig ハイパーリンクのすべての出現を、新しい形式を使用したハイパーリンクで置換します。フィルタ クラスは、抽象 System.IO.Stream クラスを実装し、要求処理の最後に応答をフィルタ処理するために ASP.NET ランタイムによって使用されます。ASP.NET の Integrated パイプライン エンジンにより、応答フィルタは、ASP.NET によって生成されたのではない応答も操作できるため、応答がバッファされている限り、この応答フィルタを使用して、PHP 応答だけでなく静的ファイルや ASP ページも処理できます。IIS 7.0 では、既定ですべての応答がバッファされるため (構成されている制限まで)、このメカニズムが機能できます。

Figure 7 フレンドリーなハイパーリンクを生成する
public class QdigSEFFilterModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.PreRequestHandlerExecute += new EventHandler(
OnPreRequestHandlerExecute);
}
public void Dispose(){}
public void OnPreRequestHandlerExecute(Object source, EventArgs e)
{
HttpApplication app = (HttpApplication)source;
HttpContext context = app.Context;
if (context.Request.Path.Equals(
VirtualPathUtility.ToAbsolute("~/index.php")))
{
// wire up the response filter
context.Response.Filter = new QdigSEFFilter(context);
}
}
}
ここで示すフィルタ実装は、ストリームにプッシュされた着信応答バイトをすべてバッファし、応答の文字セットを使用してこれらのバイトを文字列に変換し、URL の正規表現置換を実行します。次に、文字列を元の文字セットに再エンコードし、応答データをランタイムに再びプッシュします。モジュールおよび応答フィルタの完全な実装は、コード ダウンロードに含まれています。
ここでもモジュール コードを QdigSEFFilter.cs という別のソース ファイルとして App_Code ディレクトリに配置し、モジュールをアプリケーションの web.config の <modules> 構成セクションに登録します。この時点で、モジュール セクションは次のようになります。
<system.webServer>
<modules>
<remove name="FormsAuthentication" />
<add name="FormsAuthentication" type=
"System.Web.Security.FormsAuthenticationModule" />
<!-- Add the SEF url rewriting module -->
<add name="QdigSEFUrlsModule" type="QdigModules.QdigSEFModule"/>
<!-- Add the response filter module -->
<add name="QdigSEFFilterModule" type=
"QdigModules.QdigSEFFilterModule" />
</modules>
</system.webServer>
http://myphpgallery/ の Qdig ページをリフレッシュし、ログインした後で、ページ上のすべてのハイパーリンクはフレンドリーな URL を使用するように変更されました (図 8 を参照)。これまで、既存の ASP.NET 機能および ASP.NET の Integrated パイプライン用に開発された新機能でアプリケーションの機能にいくつかの重要なアップグレードを行った後に、元のアプリケーションは 1 行も変更する必要がありませんでした。
図 8 フレンドリーな URL を使用して修正したギャラリー (画像を拡大するには、ここをクリックします)
パフォーマンスをテストする
この時点で、アプリケーションのソース コードはまったく変更していませんが、Qdig にはかなりの機能が追加されました。このすべての新機能は、アプリケーションのパフォーマンスにどれぐらい影響するでしょうか。Windows 上の CGI メカニズムのプロセス起動のオーバーヘッドにより、従来、パフォーマンスは IIS プラットフォーム上の PHP アプリケーションの考慮事項でした。FastCGI は、このオーバーヘッドを除去することでアプリケーションのパフォーマンスを大幅に改善し、IIS を PHP およびその他の FastCGI 準拠アプリケーションをホストするためのはるかに魅力的なプラットフォームにするという約束を果たします。ただし、導入したアプリケーション拡張によってこのオーバーヘッドの大部分が再び追加される場合は、IIS での PHP アプリケーションの展開を考えたときに、これらのアプリケーション拡張はあまり魅力的ではなくなる可能性があります。
ただし実際には、追加した機能拡張は Qdig のパフォーマンスにほとんど影響しません。図 9 に、アプリケーションに対して実行したパフォーマンス テストの概要を示します。

Figure 9 パフォーマンスの結果
| テスト |
1 秒あたりの要求数 (多いほどよい) |
| CGI を使用した Qdig |
32 |
| FastCGI を使用した Qdig |
93 |
| FastCGI を使用した Qdig およびフレンドリー URL |
88 |
| CGI を使用した Hello.php |
51 |
| FastCGI を使用した Hello.php |
2239 |
テストは、100 個の同時仮想クライアントを使用し、サーバー上の CPU を最大限に利用して、ギャラリー内部の一連の Qdig URL を要求することにより、Microsoft Web Capacity Analysis Tool (WCAT) 6.3 で実行されました。最後のテストでは、フレンドリーな URL を Qdig URL の代わりに使用して、変換機能を利用しました。テストを簡単にするために、認証は無効にしました。
CGI から FastCGI に移行することで、スループットが約 3 倍に向上します。2 つのフレンドリー URL モジュールを追加した後、スループットの低下はわずか 5% であり、これは無視できます。
あいにく、Qdig は極端に CPU を消費するため、FastCGI によって提供可能な向上の多くが打ち消されます。比較すると、単純な hello.php スクリプトでは、CGI から FastCGI に移行したときにスループットが 43 倍に向上しました。かなりコストの高い操作である応答フィルタを追加した後でも、全体的なスループットへの影響は、アプリケーション自体のオーバーヘッドに比べれば無視できる程度です。
パフォーマンスを向上させるためにアプリケーションの外部で行えることはほとんどないため、サーバー パフォーマンスの調整に関する限り、これはほとんど最悪の状況です。アプリケーション コード自体を変更することは、アプリケーション開発者でなければ不可能なことが多く、アプリケーション開発者であっても困難な場合があります。筆者の場合は、開始時にアプリケーションは変更しないと決めたため、アプリケーションの変更はもちろん想定外です。
ASP.NET 出力キャッシング
出力キャッシュは ASP.NET の機能であり、Web アプリケーションが生成した応答を同じリソースに対する後続の要求に対して再利用できるようにします。実際に ASP.NET は、ASP.NET アプリケーション内部のアプリケーション データをそのキャッシュ API でキャッシングし、アプリケーションの応答全体を出力キャッシュでキャッシングするために、機能の拡張セットを提供します。どちらの機能も、アプリケーションのパフォーマンスを拡張でき、バックエンド リソースに対する負荷を大幅に減らすことができます。IIS 7.0 での新機能は、ASP.NET 以外のコンテンツ タイプに ASP.NET 出力キャッシュを使用できることです。これこそが、イメージ ギャラリーを少し改良するために、筆者が使用しようとしている機能です。
大量のコーディング作業を必要とし、アプリケーションの処理オーバーヘッドのチャンクを除去することの多い段階的なアプリケーション パフォーマンスの調整とは対照的に、出力キャッシングは、一定割合の要求作業負荷に対するオーバーヘッドのほぼすべてを除去する傾向にあります。その効果は、同じコンテンツがアプリケーションのユーザーによってどれぐらいの頻度で要求されるかというアプリケーションの使用の局所性によって決まります。通常のアプリケーションでは、多くの場合に 90/10 ルールが適用されます。これは基本的に、要求の 90% がコンテンツの 10% に対するものであることを意味します。このコンテンツを出力キャッシュできれば、アプリケーションのスループットと容量が 90% 近く向上する可能性があります。
当然、簡単であればすべての人が行います。出力キャッシングには一連の制限事項があります。特に、動的コンテンツに関してはキャッシング ソリューションの展開が難しくなることがあります。主な制限はコンテンツの動的な性質から生じます。たとえば、Web サイトのページでは、クエリ文字列パラメータ、時刻、またはデータベース更新に基づいてさまざまな応答が生成されることがあります。これらの考慮を怠ると、ユーザーに間違ったキャッシュ応答が返される場合があり、これは望ましくありません。さいわい、ASP.NET 出力キャッシュは、これらの制限をすべて考慮しているため、アプリケーションに出力キャッシング ソリューションを展開するための機能豊富なプラットフォームが提供されます。
ASP.NET ページ パーサーは、<%@ OutputCache %> ディレクティブをサポートしているため、多くの出力キャッシング設定を ASPX ページで直接構成できます。実際に、多くの開発者はこの方法で ASP.NET アプリケーションの出力キャッシングを有効にします。Qdig は PHP で作成されているため、この機能を利用できません。代わりに、ASP.NET HttpCachePolicy API を使用して出力キャッシュを動的に構成する別のモジュールを作成し、出力キャッシュ モジュールが Qdig の応答を正しくキャッシュできるようにしています。
public class QdigOutputCacheModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.PostAuthorizeRequest +=
new EventHandler(OnPostAuthorizeRequest);
application.PostRequestHandlerExecute +=
new EventHandler(OnPostRequestHandlerExecute);
}
public void Dispose(){}
}
今回、このモジュールは PostAuthorizeRequest および PostRequestHandlerExecute という 2 つのパイプライン イベントをサブスクライブします。最初の PostAuthorizeRequest は、出力キャッシュ モジュールが要求に対する既存の応答を検索し、それ以上処理せずに戻ろうとする直前に発生します。PostRequestHandlerExecute イベントは、ハンドラ (PHP) が応答を生成した直後、出力キャッシュ モジュールが後で使用するために応答を保存しようとする前に発生します。
PostAuthorizeRequest イベントでは、Qdig 応答がクエリ文字列パラメータによってどのように異なるかを出力キャッシュ モジュールに通知して、出力キャッシュ モジュールが正しい応答を見つけられるようにする必要があります (図 10 を参照)。クエリ文字列パラメータによって異なるキャッシュを構成して、ナビゲーション モードの変更や次のイメージに移動するための次のリンクのクリックなどのユーザー入力に応答することにより Qdig ギャラリーが正しく動作できるようにしています。この機能がないと、最初の要求で応答のキャッシングが行われ、どのイメージが要求されたかに関係なく、ギャラリーに対するその後のすべての要求に対してキャッシュの内容が返されることになります。また、キャッシュが各認証済みユーザーに独自のキャッシュ コピーを正しく提供するように、認証済みユーザーごとに変更します (GetVaryByCustomString の実装を参照)。

Figure 10 正しいキャッシュ バリエーションを確認する
public void OnPostAuthorizeRequest(Object source, EventArgs e)
{
HttpApplication app = (HttpApplication)source;
HttpContext context = app.Context;
// Make sure we only process requests to QDIG's index.php.
String requestPath = context.Request.Path;
if (!requestPath.Equals(VirtualPathUtility.ToAbsolute
("~/index.php")))
return;
// Set up the vary by querystring information
HttpCacheVaryByParams varyByParams =
context.Response.Cache.VaryByParams;
varyByParams["Qwd"] = true; // gallery path
varyByParams["Qif"] = true; // image to display
varyByParams["Qiv"] = true; // navigation mode
varyByParams["Qis"] = true; // image size
varyByParams["Qtmp"] = true;// other control information
// if user is set, also vary by user (See global.asax)
context.Response.Cache.SetVaryByCustom("AuthenticatedUser");
}
PostRequestHandlerExecute イベントでは、応答をキャッシュする必要があるかどうかを判断し、適切な設定を構成してキャッシュ可能にする必要がありました (図 11 を参照)。AddFileDependency 呼び出しに注意してください。省略されたコードでは、ギャラリー ディレクトリとイメージへの物理的なパスを判断し (指定されている場合)、ファイル依存関係を応答に追加します。ここで、出力キャッシュでサポートされている CacheDependency メカニズムを使用して、これらのリソースの変更を監視し、変更された場合はキャッシュ済みの応答を無効にします。これにより、応答を長時間キャッシュに保持し、基礎となるデータが変更された場合にのみキャッシュを無効にできます。だれかが新しいイメージをドロップするか、新しいサブギャラリーを作成した場合、キャッシュは影響を受けるキャッシュ済み応答を自動的に削除し、新しい要求は更新されたページを受け取ります。

Figure 11 キャッシュされる応答を構成する
public void OnPostRequestHandlerExecute(Object source, EventArgs e)
{
HttpApplication app = (HttpApplication)source;
HttpContext context = app.Context;
// Make sure we only process requests to QDIG's index.php.
String requestPath = context.Request.Path;
if (!requestPath.Equals(VirtualPathUtility.ToAbsolute
("~/index.php")))
return;
// Enable this response to be output cached
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetExpires(DateTime.Now +
TimeSpan.FromMinutes(5));
// code omitted for clarity ...
// add the dependencies to the response
context.Response.AddFileDependency(physicalDirectoryPath);
context.Response.AddFileDependency(physicalFileName);
}
CacheDependency メカニズムは強力な抽象化です。このメカニズムによって、Web サービスの呼び出しやレジストリ キーのチェックなど、あらゆる種類の基本データを監視できる独自の CacheDependency 実装を作成できます。組み込みの CacheDependency 実装には、ファイル、ディレクトリ、および SQL Server データベース テーブルの監視のサポートが含まれています。
配置とテスト
前に行ったのと同じように QdigOutputCache.cs ソース ファイルを App_Code ディレクトリにドロップすることで、最新モジュールを配置し、それを web.config に登録しました。
<system.webServer>
<modules>
...
<!-- Let the OutputCache module run for all requests -->
<remove name="OutputCacheModule" />
<add name="OutputCacheModule" type=
"System.Web.Caching.OutputCacheModule" />
<!-- Add the custom output caching module -->
<add name="QdigOutputCacheModule" type=
"QdigModules.QdigOutputCacheModule" />
</modules>
</system.webServer>
QdigOutputCacheModule の追加に加えて、前にフォーム認証に対して行ったのと同様に、managedHandler の前提条件を除去するために OutputCacheModule も再び追加しました。これにより、ASP.NET OutputCacheModule は、PHP コンテンツの要求中に稼働し、出力キャッシング サービスを提供できます。
最後に、GetVaryByCustomString の実装を含む global.asax ファイルを作成しました (図 12 を参照)。GetVaryByCustomString は、モジュールがキャッシュされた応答を認証済みユーザーごとに変えられるようにする関数です。この関数は、認証済みユーザーごとに別々のキャッシュ コピーを維持して、あるユーザーが間違って別のユーザーのパーソナライズ ビューを取得することを防ぐため、重要です。Qdig はパーソナライズ ビューを提供しないため、これは問題になりませんが、いずれにしてもベスト プラクティスとして示します。

Figure 12 Global.asax による GetVaryByCustomString の実装
<%@ Application language="C#" %>
<script runat="server" language="c#">
public override string GetVaryByCustomString(HttpContext context,
string s)
{
if ("AuthenticatedUser".Equals(s))
{
// vary by authenticated user
String currentUser = String.Empty;
if (context.User != null)
{
currentUser = context.User.Identity.Name;
}
return currentUser;
}
else
{
// unknown vary string
return null;
}
}
</script>
ここで、パフォーマンスがさらに改善されたかどうかを確認しましょう。前のテストを含む完全な結果を図 13 のグラフに示します。
図 13 パフォーマンスの結果 (画像を拡大するには、ここをクリックします)
予想どおり、出力キャッシングは期待を裏切りませんでした。Qdig のパフォーマンスは、ベースライン FastCGI と検索エンジン フレンドリー (SEF) URL ベンチマークでの毎秒 88 要求から、フルに利用されているサーバー上で毎秒 1386 要求に上昇しています。
全体として、ネイティブ PHP アプリケーションを CGI から FastCGI に移行し、フレンドリーな URL と出力キャッシングでアップグレードした後、最終的な結果は元のアプリケーションの 40 倍を超えるパフォーマンス向上を示しています。これは侮れません。
まとめ
ご覧のように、IIS 7.0 Integrated パイプラインを使用すると、任意のアプリケーションに強力な機能を追加でき、ASP.NET および .NET Framework の能力と便利さを他のフレームワークで作成されたアプリケーションにもたらすことができます。開発チームにとって、この機能は、既存のアプリケーションへの投資と開発者のスキル セットを利用するためのキーとなります。新しいプラットフォームに移行するためにアプリケーションを強制的にリライトする代わりに IIS 7.0 を使用すると、既存のアプリケーションでは、IIS および ASP.NET の既存の機能がもたらす追加的な改善に加え、IIS 7.0 のエンドツーエンド拡張モデルを使用して開発された新機能を利用できます。
この記事では、IIS 7.0 拡張 API で可能なことのほんのわずかしか説明していません。実行時の Web サーバーの拡張性を見ただけですが、IIS 7.0 では構成、管理、および GUI 管理レベルの拡張性も完全に提供されており、Web サーバーをニーズに合わせてアップグレードおよび形成するエキサイティングな機会が数多く作られます。IIS 7.0 の拡張についてもっと詳しく知るには、また、すぐに使用できるいくつかの IIS 7.0 プラグインをダウンロードするには、mvolo.com で筆者のブログを参照してください。
Mike Volodarsky は、Microsoft の Web プラットフォームとツール チームのテクニカル プログラム マネージャです。過去 4 年以上、ASP.NET 2.0 と IIS 7.0 の主要機能セットのデザインと開発を推進してきました。現在は、Windows Server 2008 にこれらのテクノロジの機能を活用する顧客を支援しています。