LightNode 1.0、或いはWeb APIでのGlimpseの使い方

こないだ、RedisクライアントのCloudStructuresを1.0にしたばかりですが、今回は大昔に作った自作Web APIフレームワークのLightNodeを1.0にしました。なんでドタバタやってるのかというと、.NET XRE(ASP.NET vNext)を様子見してたんですが、そろそろ今年一年どうしていくかの態度を決めなければならなくて、結論としては、OWINで行くことにしたからです。ちゃんちゃん。その辺の理由なんかは後ほど。

さて、Glimpseです。なにはなくともGlimpseです。イマドキでC#でウェブ作るんなら、まずはGlimpse入れましょう。絶対必須です。使ったことないんなら今すぐ使ってください。圧倒的なVisual Profiling!ボトルネックが一目瞭然。コンフィグも一覧されるので、普段気にしていなかったところも丸見え。データアクセスが何やってるかも一発で分かる。ちなみに、競合としては昔あったMiniProfilerは窓から投げ捨てましょう。ASP.NET開発はもはやGlimpse以前と以後で分けられると言っても過言ではない。

で、LightNode 1.0です。変更点はGlimpseにフル対応させたことで、ついでに細かいとこ直しまくりました、と。ともあれGlimpse対応が全てです。

で、作ってる間にGlimpseをWeb API(ASP.NET Web APIとは言ってない)系で使ったり、Owinと合わせて使ったりすることのノウハウも溜まったので、LightNodeの話というかは、そっちのことを放出したいな、というのがこの記事の趣旨ですね!

OwinでGlimpseを使う

Glimpse自体はOwinに対応していません。勿論、vNextへの対応も含めてSystem.Webへの依存を断ち切ろうとしたGlimpse v2の計画は随分前から始まっているんですが、Issueをずっと見ている限り、かなり進捗は悪く、難航しているようです。正直、いつ完了するか全く期待持てない感じで、残念ながら待っていても使えるようにはなりません。

しかし、そもそもGlimpseのシステムはただのHttpModuleとHttpHandlerで動いています。つまり、Microsoft.Owin.Host.SystemWebでホストしている限りは、Owinであろうと関係なく動きます。動くはずです。実際Glimpse.axdにアクセスすれば表示されるし、一見動いています。そしてGlimpseにはページ埋め込みの他、Standaloneでの起動が可能(Glimpse.axdでの右側)なのでそこから起動すると……

いくらOwinでページ作ってアクセスしても何も表示されません、データがHistoryに蓄積されません。これにめっちゃハマって以前は諦めたんですが、今回LightNodeをOwinに何が何でも対応させたくて改めて調べた結果、対策分かりました。原因としては、Glimpseはリクエストの完了をPostReleaseRequestStateで受け止めているんですが、Microsoft.Owin.Host.SystemWebでホストしてOwinによるリクエストハンドリングでは、完了してもPostReleaseRequestStateが呼ばれません。結果的にOwinでふつーにやってる限りではGlimpseでモニタできない。

対策としては、単純に手動でEndRequestを叩いてやればいいでしょう。Middlewareを作るなら

public Task Invoke(IDictionary<string, object> environment)
{
    return next(environment).ContinueWith((_, state) =>
    {
        ((state as HttpContext).Application["__GlimpseRuntime"] as IGlimpseRuntime).EndRequest();
    }, System.Web.HttpContext.Current);
}

ということになります。このMiddlewareを真っ先に有効にしてやれば、全てのOwinパイプラインが完了した際にEndRequestが叩かれる、という構造が出来上がります。System.Webをガッツリ使ったMiddlewareなんて気持ち悪いって?いやいや、まぁいーんですよ、そもそもGlimpseがSystem.Webでしか現状動かないんだから、ガタガタ言うでない。

さて、LightNodeのGlimpse対応DLLにはこのMiddlewareを最初から同梱してあります。LightNodeでGlimpse対応のConfigurationを書く場合は、以下のようになります。

public void Configuration(Owin.IAppBuilder app)
{
    app.EnableGlimpse();
    app.MapWhen(x => !x.Request.Path.Value.StartsWith("/glimpse.axd", StringComparison.OrdinalIgnoreCase), x =>
    {
        x.UseLightNode(new LightNodeOptions()
        {
            OperationCoordinatorFactory = new GlimpseProfilingOperationCoordinatorFactory()
        });
    });
}

まずEnableGlimpse、これが先のEndRequestを手動で叩くものになってます。次にMapWhenで、Glimpse.axdだけOwinパイプラインから外してやることで、LightNodeと共存させられます!ついでに、LigthNodeでのGlimpseモニタリングを有効にする場合はGlimpseProfilingOperationCoordinatorFactoryをOptionに渡してあげれば全部完了。

LightNode+GlimpseによるWeb APIモニタリング

何ができるようになるの?何が嬉しいの?というと、勿論当然まずはTimelineへの表示。

フィルター(Before/After)とメソッド本体がTimeline上で見えるようになります。これは中身何もないですが、勿論DatabaseやRedis、Httpアクセスなどがあれば、それらもGlimpseは全部乗っけることができるし、それらをWeb APIでも見ることができる。圧倒的に捗る。

そしてもう一つがLightNodeタブ。

一回のリクエストのパラメータと、戻り値が表示されます。API開発の辛さって、戻り値が見えない(クライアント側でハンドリングして何か表示したりするも、領域的に見づらかったりする)のが結構あるなーって私は思っていて、それがこのLightNodeタブで解消されます。ちなみにもし例外があった場合は、ちゃんと例外を表示します。

また、ExecutionのPhaseが以降はすべてExceptionになってるので、フィルターが遠ったパスも確認しやすいはずです。

Web APIのためのGlimpseコンフィグ

Web APIのためにGlimpseを使う場合、ふつーのWeb用のコンフィグだと些か不便なところがあるので、調整したほうがいいでしょう。私のお薦めは以下の感じです。

<!-- GlimpseはHUDディスプレイ表示のためなどでレスポンスを書き換えることがありますが、勿論APIには不都合です。
     デフォルトはPersistResults(結果のHistory保存のみ)にしましょう -->
<glimpse defaultRuntimePolicy="PersistResults" endpointBaseUri="~/Glimpse.axd">
    <tabs>
        <ignoredTypes>
            <!-- OWINで使うならこれらは不要でしょう、出てるだけ邪魔なので消します -->
            <add type="Glimpse.AspNet.Tab.Cache, Glimpse.AspNet" />
            <add type="Glimpse.AspNet.Tab.Routes, Glimpse.AspNet" />
            <add type="Glimpse.AspNet.Tab.Session, Glimpse.AspNet" />
        </ignoredTypes>
    </tabs>
    <runtimePolicies>
        <ignoredTypes>
            <!-- クライアントがクッキー使うとは限らないので、無視しましょう、そうしないとHistoryに表示されません -->
            <add type="Glimpse.Core.Policy.ControlCookiePolicy, Glimpse.Core" />
            <!-- 404とかもAPIならハンドリングして表示したい -->
            <add type="Glimpse.Core.Policy.StatusCodePolicy, Glimpse.Core" />
            <!-- Ajaxじゃないなら -->
            <add type="Glimpse.Core.Policy.AjaxPolicy, Glimpse.Core" />
            <!-- リモートで起動(APIならそのほうが多いよね?)でも有効にする -->
            <add type="Glimpse.AspNet.Policy.LocalPolicy, Glimpse.AspNet" />
        </ignoredTypes>
    </runtimePolicies>
</glimpse>

defaultRuntimePolicyと、そして特にControlCookiePolicyが重要です。利用シチュエーションとしてStandalone Glimpseで起動してHistoryから結果を見る、という使い方になってくるはずなので(というかWeb APIだとそうしか方法ないし)、Cookieで選別されても不便すぎるかな、ブラウザからのAjaxならともかくモバイル機器から叩かれてる場合とかね。

さて、それは別として、様々なクライアントからのリクエストが混ざって判別できないというのも、それはそれで不便です。これを区別する手段は、あります。それは、クッキーです(笑) 判別用にクッキーでID振ってやるとわかりやすくていいでしょう。例えば以下の様な感じです。

var req = WebRequest.CreateHttp("http://localhost:41932/Member/Random?seed=13");
// "glimpseid" is Glimpse's client grouping key
req.CookieContainer = new CookieContainer();
req.CookieContainer.Add(new Uri("http://localhost:41932"), new Cookie("glimpseid", "UserId:4"));

glimpseidというのがキーなので、例えばそこにユーザーIDとか振っておくと見分けがついてすごく便利になります。

こんな感じです。これはデバッグビルド時のみといった形で、クライアントサイドで埋め込んであげたいですね。

LightNodeを使う利点

というわけでGlimpseとの連携が超強力なわけですが、LightNode自体はまず言っておくと、誰にでも薦めはしません。この手のフレームワークで何より大事なのが標準に乗っかることです。C#での大正義はASP.NET Web APIです、そこは揺るぎません。その上でLightNodeの利点は「シンプルなAPIがシンプルに作れる」「Glimpseによる強力なデバッグ支援」「クライアントコード自動生成」です。特に非公開のインターナルなWeb API層向けですね。反面お薦めしないのは、RESTfulにこだわりたい人です。LightNodeは設計思想として徹底的にRESTfulを無視してるんで、準拠するつもりは1ミリもありません。例えば、インターナルなAPIでRESTfulのために1つのURIを決めるのに3日議論するとか、凄まじく馬鹿げているわけで。LightNodeは悩みを与えません、そもそもメソッド書くしかできないという制約を与えているから。

凝ったルーティングもアホくさい。インターナルなWeb APIで、モバイル機器からのアクセスを前提にすると、クライアントサイドでのAPIライブラリを書くことになりますが、ルーティングが凝っていれば凝っているほど対応が面倒くさいだけ、という。嬉しさなんて0.1ミリもない。結局、ルールはある程度固定のほうが良いんですよ。さすがにパブリックAPIなら長いものに適当に巻かれて適当に誤魔化しますが。

というわけで、どういう人に薦めるかというと「とりあえずサクッとWeb API作りたい人」「モバイルクライアントからアクセスするインターナルなWeb APIを作りたい人」ですかねー。別にパブリックなのも作れないことはないですけど、別にそこまで違和感あるURLになるわけでもないですしね。

ちなみに、MVCとの共存は可能です。例えば

public void Configuration(IAppBuilder app)
{
    app.Map("/api",  x =>
    {
        x.UseLightNode();
    });
}

といった感じにapi以下をLightNodeのパスってことにすればOK。それ以外のパスではASP.NET MVCが呼ばれます。ルートが変わるだけなので、他のコンフィグは不要です。あんまり細かくゴチャゴチャやると辛いだけなので、このぐらいにしておくのがいいですね。ちなみに、Owinで困るのはHttpModuleとの共存だったりします。実行順序もグチャグチャになるし(一応、少しはOwin側でコントロールかけられますが、辛いしね)同じようなものが複数箇所にあるというのは、普通にイけてない。これはMiddlewareのほうに寄せていきたいところ。脱HttpModule。

まとめ

あ、で、OWINな理由って言ってませんでしたっけ。なんかねー、XREは壮大すぎて危険な香りしかしないんですよ。少なくとも、今年の頭(今)に、今年に使う分のテクノロジーを仕込むには、賭けられないレベルで危なっかしい。Previewで遊びながら生暖かく見守るぐらいがちょうどいいです。まあ、アタリマエだろっていえばアタリマエ(ベータすら出てないものを実運用前提で使い出すとかマジキチである)ですけどね、だから別にXREがダメとかそういう話じゃないですよ。むしろXREはまだ評価できる段階ですらないし。

で、今は過渡期で宙ぶらりんなのが凄く困る話で、そのブリッジとしてOWINはアリかな、と。OWIN自体の未来は、まぁASP.NET 5はどうしてOWIN上に乗らなかったのかにあるように、Deadでしょう。しかし、今から来年の分(XREが実用になった世代)を仕込むには、System.Webへの依存の切り離しや、Owin的なパイプラインシステムへの適用は間違いなく重要。OWINならコーディングのノリもASP.NET 5と変わらないしコードの修正での移行も容易になる、最悪互換レイヤーを挟んで適用できるので、「今」の選択としては、消極的にアリです。

ASP.NET Web APIは、うーん、ASP.NET MVCとの統合が見えてる今、改めて選びたくない感半端ないんだよねぇ。GlimpseはASP.NET Web API対応しないの?というと、そういう話もあるにはあったようですが、色々難航していて、PullRequestで物凄く時間かけて(70レス以上!一年近く!)、それでも結局取り込まれてないんですよ。ここまで来るともはやGlimpse v2でのvNext対応でMVC統合されてるんだからそれでいいじゃん、に落ち着きそうで、恐らくもう動きはないでしょう。とか、そういう周辺のエコシステムの動きも今のASP.NET Web APIは鈍化させる状況にあるわけで、あんまポジティブにはなれないなぁ。とはいえ、現状のスタンダードなWeb API構築フレームワークとして消極的にアリ、と言わざるをえないけれど。ちなみにNancyは個人的には全くナシです、あれのどこがいいのかさっぱりわからない。

Glimpseの拡張は、ちょうど社内用拡張も全部書き換えたりして、ここ数日でめちゃくちゃ書きまくったんで、完全に極めた!うぉぉぉぉ、というわけで拡張ガイダンスはいつかそのうち書くかもしれませんし、多分書きません。つーかGlimpseちゃんと日本の世の中で使われてます?大丈夫かなー、さすがにGlimpseは圧倒的に良いので標準レベルで使われなければならないと思うのですけれど。

あー、で、LightNodeは、まあ良く出来てますよ、用途の絞り方というか課題設定が明確で、実装もきっちりしてありますし。うん、私は好きですけど(そりゃそうだ)、人に薦めるかといったら、Microsoftの方針がOwin的なオープンの流れから、やっぱり大Microsoft的なところに一瞬で戻ったりしてるんで(Hanselmanには少し幻滅している)、まぁ長いものには巻かれておきましょう。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive