Azureで独自のハンドラ ( IIS と IHttpHandler )

| コメント(0) | トラックバック(1) このエントリーを含むはてなブックマーク

WorkerRole と HttpListener で、「WorkerRoleでHttpListenerを単純に使うわけにはいかないようである」となってしまい悲しい状況である。

とはいえ Azure では IIS 経由があるので、そっち経由で IronRuby につながらないかと思い調査してみる。

ASP.NET の仕組み

まずは IIS と ASP.NET がどのように繋がっているか、全く分からないので、調べてみた。

以前FITEAで小野さんに講演していただいた時に詳細を聞いた記憶があったので、探してみると どっとねっとふぁんBlog : ASP.NET の仕組み が見つかった。

大まかな手順

記事を読んでいくと

ASP.NET がリクエストを受け取ったときに最初に生成されるのが HttpContextオブジェクトです。HttpContext オブジェクトにはブラウザからのリクエストの内容が 格納されるだけでなく、ユーザ情報やキャッシュ、アプリケーション、セッションといったオブジェクト、そしてブラウザに送るレスポンスの内容も格納されます。各ページの処理で利用する Application、Cache、Request、Response、Session といった組み込みオブジェクトは、実際には HttpContext オブジェクトの中身を参照していると考えられます。

と記述があり、「HttpContext」が肝のようである。

さらに

これらのクラスはIHttpHandlerインターフェースを実装しているか、またはIHttpHandlerインターフェースを実装しているクラスをよびだすIHttpHandlerFactoryインターフェースを実装しています。このIHttpHandlerインターフェースがASP.NETの仕組みでは重要な役割を担っており、IHttpHandlerインターフェースが持つProcessRequestメソッドが呼び出されることで各ページの処理が開始されるのです。Pageクラス(.aspxファイル)もその仕組みの中で動作していることは、PageクラスがIHttpHandlerを継承していることからもわかります。 このような仕組みを理解していると、IHttpHandlerインターフェースを実装するクラスを作成し、web.configに登録することで独自のリクエスト処理機能を組み込んでASP.NETを拡張することが可能になります。実際にマイクロソフトのサイトの中では.mspxという拡張子が利用されていますが、これはIHttpHandlerインターフェースを利用した拡張例と考えられます。

を踏まえると「IHttpHandler」が重要そうである。

「HttpContext」がHttpListenerでいう「HttpListenerContext」に相当し、 「IHttpHandler」は、HttpListenerでいうと「HttpListenerのGetContextが戻ってきたときに渡るハンドラ」と 考える事が出来る。

これを踏まえると

  • IHttpHandlerインタフェースのクラスを作る
  • IISに作ったインタフェースに処理が渡るように設定する

をすればよいかと考えた。

IHttpHandlerとweb.config

参考にしたのは Merlin/Main/Hosts/IronRuby.Rack at master from jschementi's ironruby - GitHub IronRubyとIIS用のRackハンドラ。(おい

しかも、解説記事があります。(おい Rack ベースの Web アプリを IIS で動かしてみました

車輪の再実装だといわれようが、理解をするためには必要なので、挑戦する。 (Azure上で動かしたいので、自分が理解してないものをポーティングなんて出来ないしね)

IHttpHandlerの作り方

まず、Factoryが必要そうである。(もしかしたら必要ないかもしれない)

とりあえず IronRuby 用の Handlerを参考にしつつ以下のようにしてみた。

    public class HttpHandlerFactory : IHttpHandlerFactory
    {
        private static readonly object GlobalLock = new object();
        private static HttpHandler Handler;

        public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
        {
            if (Handler == null)
            {
                lock (GlobalLock)
                {
                    if (Handler == null)
                    {
                        try
                        {
                            Handler = new HttpHandler();
                        }
                        catch (Exception e)
                        {
                            context.Response.StatusCode = 200;
                            return null;
                        }
                    }
                }
            }
            return Handler;
        }
        public void ReleaseHandler(IHttpHandler/*!*/ handler)
        {
        }
    }
 
 

Factory は IHttpHandlerFactory を継承して作る必要があり、 ReleaseHandlerとGetHandlerを実装する必要があるようである。

GetHandlerは、HttpHandler インスタンスを作り返却する責任があるようだ。 このHttpHandlerは、自分用のハンドラーでこの後実装する。

次に HttpHandler であるが、これは、IHttpHandlerを継承して作る必要があるようである。

    public sealed class HttpHandler : IHttpHandler
    {
        // from IHttpHandler
        public bool IsReusable
        {
            get { return true; }
        }

        // from IHttpHandler
        public void ProcessRequest(HttpContext context)
        {
            lock (this)
            {
                try
                {
                    HttpServerProcess(context);
                }
                catch (Exception ex)
                {
                    var s = "<html><head><title>error</title></head>\n";
                        s += "<body><h1>Exception</h1>\n";
                        s += "<pre>\n";
                        s += System.Web.HttpUtility.HtmlEncode( ex.Message );
                        s += "\n";
                        s += System.Web.HttpUtility.HtmlEncode( ex.StackTrace );
                        s += "\n";
                        s += "</pre>\n";
                        s += "</body></html>\n";
                        context.Response.Write(s);
                        context.Response.ContentType = "text/html; charset=utf-8";
                }
            }
        }
        private void HttpServerProcess(HttpContext context)
        {
            var req = context.Request;
            var res = context.Response;
            res.Write("<html><head><title>タイトル</title></head><body>hello</body></html>");
            res.ContentType = "text/html; charset=utf-8";
		}
    }
 
 

ProcessRequest及びIsReusableの実装が必要で、 実際の処理はProcessRequestの中で行うようである。

今回は簡単なhtml文章を返却する。

これらのソース

Windows Azure の WebRole (ASP.NET) のプロジェクトに C# の クラスソースファイル(.cs)を追加して そのソースコードに追加した。

本来なら、別のDLL(アセンブリ)にすればいいハズであるが、経験不足でどのようにするのが良いか 判断付かなかったので、この方法をとっている。

web.config

    <system.web>
?省略?

      <httpHandlers>

?省略?

        <add verb="*" path="*.xxx" validate="false" type="IronRubyHosting.HttpHandlerFactory, WebRole1" />

?省略?

      </httpHandlers>

?省略?

    </system.web>

?省略?

    <system.webServer>
?省略?
      <handlers>
?省略?
        <add name="ironruby" verb="*" path="*.xxx"  type="IronRubyHosting.HttpHandlerFactory, WebRole1" resourceType="Unspecified" />
?省略?
      </handlers>
    </system.webServer>
 
 

できあがり?

これで http://localhost/nanntoka.xxx のように 拡張子 xxx でアクセスすると 自分のハンドラーに飛んできて返答を返す。

おわりに

ここまで、すんなりきているようであるが、実は3回ほどプロジェクトを作りなおしてる。

とりあえず、IISからIHttpHandler経由でProcessRequestを呼び出す事が出来た。

参考

トラックバック(1)

トラックバックURL: http://www.m-tea.info/mt-tb.cgi/60

Azureで独自のハンドラ ( IIS と IHttpHandler ) で、独... 続きを読む

コメントする

あわせて読みたいブログパーツ

このブログ記事について

このページは、k1ha410が2010年1月 8日 23:50に書いたブログ記事です。

ひとつ前のブログ記事は「IronRuby と C# バインディングの注意点2」です。

次のブログ記事は「Azure の IISからIronRuby」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。