はじめに
HTTPハンドラは、ブラウザからの要求に応える役割を担っています。ブラウザが管理する要求は、ファイル拡張子(またはその欠如)に基づいて処理されるか、ハンドラを直接呼び出すことによって処理されます。1つの要求で呼び出せるハンドラは1つだけです。ハンドラは、.aspxファイルや.ascxファイルのような静的なHTMLテキストで構成されるものではありません。ハンドラは、IHttpHandlerインターフェイスを実装するクラスです。セッション情報をやり取りする必要がある場合は、IRequiresSessionStateも実装する必要があります。また、非同期ハンドラが必要な場合は、IHttpHandlerインターフェイスではなく、IHttpAsyncHandlerインターフェイスを実装しなければなりません。
ファイル拡張子によるハンドラの呼び出し
ハンドラは、「web.config」とIISファイル拡張子マッピングでマップされているファイル拡張子によって起動することができます。例えば「something.billybob」という名前のファイルが要求された場合は、「billybob」というファイル拡張子に基づいてハンドラが起動されます。「http://web/handler/file」のようにファイル拡張子がない要求が行われた場合は、「*.*」またはディレクトリ「*」にマップされているハンドラが起動されます。
ハンドラの直接呼び出し
Webサーバー上でのハンドラのコードファイルの拡張子は「ashx」です。このファイルは、「web.config」やIISファイル拡張子マッピングを設定しなくても、例えば「http://web/handler/handler.ashx」と指定して直接呼び出すことができます。このタイプのハンドラの例としては、フォトアルバム、RSSフィード、ブログサイトなどがあります。これらは、標準のHTMLなしでも適切に実現できる機能の好例です。フォトアルバムは、ディレクトリのクロールを行って画像を返す処理を行います。RSSフィードは、必要な情報を適切な形式で返す処理を行います。
サンプルハンドラ(Trace.axd)
ファイル拡張子で起動されるハンドラの例として、デバッグに使用される「Trace.axd」ファイルを考えてみましょう。「Trace.axd」ハンドラを起動するためには、「web.config」にtraceセクションを追加することによって、Webサイトにトレースを設定します。
<configuration>
<system.web>
<trace enabled="true"/>
</system.web>
</configuration>
その後、「http://localhost/trace.axd」などと指定して、Webサイトのルートから「Trace.axd」ファイルを呼び出します。
ハンドラをファイル拡張子で起動するためのIISマッピング
要求のファイル拡張子に基づいてハンドラを起動するためには、「web.config」およびIISファイル拡張子マッピングで設定を行う必要があります。「*.axd」ファイルのファイルマッピングを確認するには、IISマネージャを開き、アプリケーションを設定します。[Mappings]タブを表示し、下方向にスクロールして.axdファイル拡張子を表示します。
.axd拡張子をダブルクリックすると、このマッピングの詳細が表示されます。
ファイル拡張子(またはその欠如)に基づいてハンドラを起動する方法には、何をいつ呼び出すかを柔軟に制御できるというメリットがあります。ファイル拡張子ハンドラに関する記事はWeb上でいくつも紹介されているため、本稿では、直接呼び出されるハンドラに焦点を当てます。
ハンドラの作成
ハンドラコードファイルを作成するには、Visual Studio 2005(.NET 1.xハンドラの場合はVisual Studio 2003)のWebサイトが必要です。新しいWebサイトとして、[ASP.NET Web Site]を選択します。
「App_Data」ディレクトリと「default.aspx」ファイルから成るWebサイトが作成されます。「default.aspx」は使用しないので、そのまま残しておいても、削除しても構いません。
ソリューションエクスプローラでWebサイトをクリックして「web.config」とハンドラファイルを作成し、「web.config」と「Generic Handler」の新しいアイテムを追加します。
ハンドラコードファイルは、次のようになります。
<%@ WebHandler Language="C#" Class="Handler" %>
using System;
using System.Web;
public class Handler : IHttpHandler {
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "text/plain";
context.Response.Write("Hello World");
}
public bool IsReusable {
get {
return false;
}
}
}
ここでプロジェクトをビルドし、「handler.ashx」ファイルをスタートアップファイルとして設定します。デバッグを開始します。デバッグを可能にするよう「web.config」を変更してもよいかを確認するメッセージが表示されます。デバッグを可能にすることを自分自身で明示的に指定する(<compilation debug="true"/>とします)場合を除いては、ここで変更を受け入れます。
筆者のデスクトップは「text/plain」をXMLとして解析するようにセットアップされているため、筆者のブラウザでは次のようなエラーが発生します。
正しく動作させるためには、Visual Studioによって生成された「handler.ashx」を修正する必要があります。そこで、コンテンツタイプを「text/plain」から「text/html」に変更します。ビルドとデバッグを再び実行します。次のような応答が得られます。
Handler.ashx
「handler.ashx」ファイルはIHttpHandlerインターフェイスを実装しており、ProcessRequestとIsReusableという2つのメソッドを備えています。ProcessRequestは、必要な処理コードを記述するメインのメソッドです。IsReusableメソッドは、別の要求がIHttpHandlerインスタンスを使用できるかどうかを指定するメソッドであり、既定はtrueに設定されています。インスタンスが再利用可能な場合はtrueに設定します。これによって、ハンドラの処理速度が向上し、サーバーが実行しなければならない処理が削減されます。しかし、ステートや非同期が原因でインスタンスを再利用できない場合は、IsReusableをfalseに設定する必要があります。
セッションステート
次に進む前に、「handler.ashx」のコードを変更してIRequiresSessionStateインターフェイスを実装します。セッションに対して読み取り専用のアクセスが必要な場合は、IReadOnlySessionStateインターフェイスを実装します。どちらのインターフェイスもSystem.Web.SessionState名前空間にあり、using構文を使用して追加する必要があります。コードは次のようになります。
<%@ WebHandler Language="C#" Class="Handler" %>
using System;
using System.Web;
using System.Web.SessionState;
public class Handler : IHttpHandler , IReadOnlySessionState{
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "text/html";
context.Response.Write("Hello World");
}
public bool IsReusable {
get {
return false;
}
}
}
クラス内の先頭行にブレークポイントを設定し、デバッグを再び開始します。
デバッグビルドでのデバッグ
デバッグビルドを使用するときは、Visual Studioデバッガのすべてのリソース、すべての.NET Framework名前空間、およびすべてのユーティリティとアプリケーションを利用できます。Visual Studioデバッガでは、コールスタック、ローカル変数、ウォッチ変数などを確認できます。読者の皆さんは、これらの機能にある程度は精通していることでしょう。しかし、HTTPハンドラの場合、デバッグはこれまで以上に重要です。それは、目に見える形で現れるバグは、すべてコンテンツにコーディングした内容であるためです。
リリースビルドでのデバッグ
リリースビルドは、機能とパフォーマンスの両方の面でデバッグビルドとは異なります。リリースビルドを使用しているときは、おそらく運用環境にあります。そのような場合は、異なるツールセットを使用して問題箇所を見つける必要があります。
System.Diagnostics名前空間によるデバッグ
Diagnostics名前空間には、リリースモードとデバッグモードでのデバッグに役立つクラスが数多く用意されています。リリースモードでデバッグする場合は、Traceクラスを使用します。デバッグモードでデバッグする場合は、Assertクラスを使用します。本稿では、この名前空間のすべての機能を取り上げるわけではありませんので、当面の問題解決に役立つクラスは各自で調べてください。
これらのクラスの使い方を理解するために、この名前空間を使用して、無限ループを使用するようにコードを変更してみましょう。アサーション内の式がfalseのときにエラーがスローされるので、以下の例ではカウンタが10に達するとエラーがスローされます。
<%@ WebHandler Language="C#" Class="Handler" %>
using System;
using System.Web;
using System.Web.SessionState;
using System.Diagnostics;
public class Handler : IHttpHandler , IReadOnlySessionState{
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "text/html";
context.Response.Write("Hello World");
int i=0;
while (1!=0)
{
Debug.Assert(i<10);
i++;
}
}
public bool IsReusable {
get {
return false;
}
}
}
ビルドとデバッグを行ってみましょう。ブレークポイントが設定されていないため、アサーションがヒットするとデバッガが再びアクティブになります。発見しようとするエラーがあらかじめ分かっていて、それをテストできる場合には、この方法が適しています。まずアサーションウィンドウが表示され、アサーションに関する情報が表示されます。デバッガをアクティブにしたい場合は、[Retry]ボタンをクリックします。これによりデバッガが表示され、問題のアサーションコード行が示されます。
特定のBoolean文でテストするのではなく、おおまかなコード位置を調べたい場合は、コード内でDebugger.Break文を使用します。この場合も、デバッガが表示されます。前提条件を調べ、なおかつエラーで停止する場合は、この2つの文を組み合わせることで、デバッグのプロセスを効率化することができます。このコードの例を以下に示します。
<%@ WebHandler Language="C#" Class="Handler" %>
using System;
using System.Web;
using System.Web.SessionState;
using System.Diagnostics;
public class Handler : IHttpHandler , IReadOnlySessionState{
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "text/html";
context.Response.Write("Hello World");
Debugger.Break();
int i=0;
while (1!=0)
{
Debug.Assert(i<10);
i++;
}
}
public bool IsReusable {
get {
return false;
}
}
}
デバッグセッションの中で、Debug.Write、Debug.WriteIf、Debug.WriteLine、およびDebug.WriteLineIfを使用すると、出力ウィンドウに値を書き出すことができます。これは、HTTPハンドラの状態を表示するのに役立ちます。
リリースビルドで問題の原因を突き止めるには、コードを調べる手段が必要です。1つの方法は、コードにEventLog.WriteEntry文を追加することです。これによって、HTTPハンドラの実行時にイベントログに情報を追加することができます。WriteEntryメソッドは非常に負荷がかかるため、各自の環境に適した最善の方法を判断する必要があります。イベントビューアに単純な情報エントリが表示されるようにするには、WriteEntryメソッドに1つのString引数を指定します。イベントビューアはイベントの「ソース」についての情報を要求するので、これを「HTTPHandler-DebugArticle」と名付けます。これにより、イベントビューアで簡単に確認できるようになります。コードは次のようになります。
<%@ WebHandler Language="C#" Class="Handler" %>
using System;
using System.Web;
using System.Web.SessionState;
using System.Diagnostics;
public class Handler : IHttpHandler , IReadOnlySessionState{
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "text/html";
context.Response.Write("Hello World");
int i=0;
// Create an EventLog instance and assign its source.
EventLog myLog = new EventLog();
myLog.Source = "HTTPHandler-DebugArticle";
while (i<100)
{
if (i/10==0)
{
// Write an informational entry to the event log.
myLog.WriteEntry("Writing to event log: i is divisible by 10.");
}
i++;
}
}
public bool IsReusable {
get {
return false;
}
}
}
ハンドラのリリースバージョンをビルドし、実際に呼び出してみましょう。出力には、「Hello World」というテキストだけが含まれます。それではイベントビューアを開き、アプリケーションログを開いてください。「HTTPHandler-DebugArticle」というソースからの多くの情報が表示されます。
このうちいずれかのイベントを開くと、簡単な出力が表示されます。
System.Diagnostics名前空間には、パフォーマンスモニタと連携するクラスや、ロジックフローのデバッグに役立つプロセスやクラスもあります。
プロセスにアタッチして実行中のHTTPハンドラをデバッグする
問題箇所を見つけるもう1つの方法として、HTTPハンドラを含むプロセスにアタッチする方法があります。複数のWebサイトを実行していて、各Webサイトがそれぞれ独自のプロセス空間にある場合は、プロセスIDを判別しなければならない可能性があります。ハンドラにこれを指定させるのが、最も簡単な方法です。System.Diagnostics名前空間にはProcessというクラスがあり、現在のプロセスIDを判断するのに役立ちます。IDを見つけるには、コードを次のように変更することで、イベントビューアにIDを送ります。
<%@ WebHandler Language="C#" Class="Handler" %>
using System;
using System.Web;
using System.Web.SessionState;
using System.Diagnostics;
public class Handler : IHttpHandler , IReadOnlySessionState{
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "text/html";
context.Response.Write("Hello World");
int i=0;
Process.GetCurrentProcess().Id.ToString();
// Create an EventLog instance and assign its source.
EventLog myLog = new EventLog();
myLog.Source = "HTTPHandler-DebugArticle";
myLog.WriteEntry("Process Id is " + Process.GetCurrentProcess().Id.ToString());
while (i<100)
{
if (i/10==0)
{
// Write an informational entry to the event log.
myLog.WriteEntry("Writing to event log: i is divisible by 10.");
}
i++;
}
}
public bool IsReusable {
get {
return false;
}
}
}
ここで重要なコード行は、ループ内のコード行と、myLog.WriteEntry:Process.GetCurrentProcess().Id内の文字列を置き換えるコード行です。次の図は、イベントビューアのエントリの例を示しています。
プロセスIDが分かったら、このIDをタスクマネージャで探す必要があります。タスクマネージャでプロセスIDを探すには、[プロセス]タブをクリックし、[表示]をクリックして、[列の選択]を選択します。[PID(プロセスID)]チェックボックスをオンにし、[OK]をクリックしてダイアログを閉じます。プロセスのリストに[PID]列が表示されるので、列名をクリックしてソートできます。リスト内で目的のプロセスIDを見つけたら、これを右クリックし、[デバッグ]を選択します。これによってVisual Studioが開始します。Visual Studioからコードファイルを開き、そこでデバッグを行います。
まとめ
ASP.NET HTTPハンドラは単純なクラスであり、これを使用することで要求を処理し、ブラウザに応答を返すことができます。ハンドラによって、現在のWebコンテキストとセッションステートにアクセスできます。Visual Studio 2005で提供される既定の「Generic Handler」は、現在のコードでは動作しない可能性があります。いくつかの変更を加え、System.Diagnostics空間と連携することで、ビルドとデバッグが正しく行われるHTTPハンドラを作成することができます。