前回はNancyFx と TopShelf を使った SelfHost な APIサーバーについて紹介しました。
しかしプロダクション環境に投入する前に Nancy を辞めて、LightNode に完全移行しました。
そこで今回は、なぜLightNode にしたのかについて書きたいと思います。
目次
なぜLightNode にしたのか
NancyFxを選んだのは、Owin と組み合わせての View と API の提供にちょうど良かったからです。Wikiも豊富で記事もそれなりにありますしStackOverflow に回答も多いです。
では、なぜ情報も豊富な NancyFx から LightNode にしたのかというと、LightNode の方が圧倒的にお手軽でシンプルだったためです。
www.slideshare.net
LightNodeがシンプルなのは、機能の取捨選択を行って Owinに任せられることは任せて機能を最小限にとどめていることにあります。
記事にもある通り、LightNodeにはある意味で制限があります。しかしシンプルというのはとても大事で、Nancy の時に苦しんだことの多くが LightNode を使うことで解消されることが移行前にはっきりしていました。
Nancyにあるツラさ | LightNode で得られること |
---|---|
Moduleで常にルーティングを意識する必要がある | {ClassName}/{MethodName} の単純なルールで縛られておりルーティングを考える必要がない |
リクエスト処理を都度書く必要がある | POST は フォームパラメータ、GET は クエリストリングのルールで統一されている |
APIを実行するクライントコードを別途書く必要がある | T4 でサーバーサイドコードからの自動生成が可能 |
API のテストをしようにも Swagger の導入が別途必要 | SwaggerでのAPIテストも容易 (パッケージがNuGetで配布されていて導入が超絶楽) |
実行の計測がめんどください | Glimps での計測も簡単 (同上) |
移行当初は SelfHost にまつわるバグや View周りのめんどくささもありましたが、すでに修正されており利用で困ることはないでしょう。
SelfHost で受ける制限
先に、SelfHost の場合の制限を挙げておきましょう。
Context と Glimpse があります。
Glimpse はIIS依存のため、現在利用できませんが v2 で Owinサポートするよ!っといわれていますがいつになることやら。。。。
HttpListener のころからあるContextが空っぽな件に関しては、OwinRequestScopeContext ミドルウェアを LightNode の前にはさみ込めば ContextをLightNodeで利用できるので問題ありません。
それ以外は特に制限も感じずに利用できるので大変便利です。
リポジトリ
GitHub に今回の記事で作成したソリューションを置いておきます。
今回は、前回作成したNancy + TopShelf な SelfHost をLightNodeに移行させるようにします。
早速みていきましょう。今回は VS2015 RC で作っていきます。
LightNode の導入と移行
前回の NancySelfHost のプロジェクトを LightNodeSelfHost にいじります。
Nancy で追加された、いらないビルドイベントも消します。
NuGet
すでに Owin 系は入っていますが Nancy などは邪魔なのでけしましょう。
もとあったのが、
こんな感じですかね。
続いて、さくさくっとNugetでパッケージを入れていきます。Package Manager Console で次のコマンドを入れていきましょう。
まず Owin 系です。Microsoft.Owin.StaticFiles は Microsoft.Owin.FileSystem と共にローカルにある js や css を参照させるために使います。仕方ない...。
Microsoft.AspNet.WebPages は、RazorEngine を今回 ViewEngine に使うのですが、インテリセンスのために bin直下に置く必要がアリマシテ!*1
Install-Package Microsoft.Owin.StaticFiles Install-Package Microsoft.AspNet.WebPages
今回は、NancySelfHost からの移行なのでこの程度ですね。新規で作るならMicrosoft.Owin.Host.HttpListener
とかもいりますよ!
LightNode関連です。今回は、JsonFomatter と Swagger 統合まで入れます。
Install-Package LightNode.Server Install-Package LightNode.Formatter.JsonNet Install-Package LightNode.Swagger
ViewEngine です。今回はRazorEngine を使うことにします。Razor便利!
Install-Package RazorEngine
エラーの嵐
当然ですが Nancy の参照を消したのでエラーの嵐です。エラーをまずきれいになくしましょうか。
LightNode において、Nancy でやっていた Modules によるルーティングは不要です。消します。
NancyBootstrapper や NancyPathProviderも不要ですね。消します。
最後に、Nancy を使わないので Startup.cs から UseNancy();
を消します。
これでエラーは消えましたね。
最後に、UseWebApi もいらなくなったので消します。
Configuration への LightNode 組み込み
まずは、LightNode を Startup.cs で読み込むように Configuration を触ります。
事前処理
using のエラーは、Ctrl + .
か R# なら Alt + Enter
で処理できます。嫌な方は次の using で。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web.Http; using System.IO; using System.Web.Http.ValueProviders; using Microsoft.Owin; using Microsoft.Owin.Hosting; using Owin; using LightNode.Server; using LightNode.Formatter; using LightNode.Swagger;
OwinRequestScopeContext で Context 取得できるようにUseRequestScopeContext()
します。
public void Configuration(IAppBuilder app) { // 各種有効化 app.UseRequestScopeContext(); // Context の取得 }
続いて、ローカルファイルの css などをOwinから呼びだせるように UseFileServer()
します。
public void Configuration(IAppBuilder app) { // 各種有効化 app.UseRequestScopeContext(); // Context の取得 app.UseFileServer(); // FileServer 用の読み込みだよ (Owin からFileアクセス許可を許容するために必要) }
Api処理
続いてApi の基底ルートを定めます。今回は /api
で入ってきたものを api処理としましょう。
この辺は LightNode のドキュメントにもあります。
LightNodeOptions(AcceptVerbs.Get | AcceptVerbs.Post
で、Get と Post を受け入れています。
new LightNode.Formatter.JsonNetContentFormatter(), new LightNode.Formatter.JsonNetContentFormatter()
で JsonNetをフォーマッターとして使用します。
あとはエラーのハンドリングですね。まぁ書いてある通りで!
// api 処理 app.Map("/api", builder => { var option = new LightNodeOptions(AcceptVerbs.Get | AcceptVerbs.Post, new LightNode.Formatter.JsonNetContentFormatter(), new LightNode.Formatter.JsonNetContentFormatter()) { ParameterEnumAllowsFieldNameParse = true, // If you want to use enums human readable display on Swagger, set to true ErrorHandlingPolicy = ErrorHandlingPolicy.ReturnInternalServerErrorIncludeErrorDetails, OperationMissingHandlingPolicy = OperationMissingHandlingPolicy.ReturnErrorStatusCodeIncludeErrorDetails, }; // LightNode つかうよ builder.UseLightNode(option); });
Page処理
APIだけじゃなくて、ブラウザでアクセスがあったらページを表示したくないですか?そのためのルートとして /pages/
を切ってみましょう。
といっても、処理はLightNodeで行うので全然かわらないのですが!
// page 処理 app.Map("/pages", builder => { // LightNode つかうにゃ builder.UseLightNode(new LightNodeOptions(AcceptVerbs.Get, new JsonNetContentFormatter())); });
Swagger処理
Swagger を使って APIのテストが簡単にできるのは間違いなく LightNode の良さです。やり方も簡単です。まずはSwagger を使うためのルートと、メソッドのxmlを指定します。
// Swagger くみこむにゃん app.Map("/swagger", builder => { var xmlName = "LightNodeSelfHost.xml"; var xmlPath = AppDomain.CurrentDomain.BaseDirectory + @"bin\" + xmlName; builder.UseLightNodeSwagger(new SwaggerOptions("LightNodeSelfHost", "/api") { XmlDocumentPath = xmlPath, IsEmitEnumAsString = true // Enumを文字列で並べたいならtrueに }); });
プロジェクトのプロパティで指定したパスに xmlを吐き出しましょう。出力 > XMLドキュメントファイル がソウデスネ。
一度ビルドして、生成された XML を常にコピーされるようにすれば準備ok です。
Api の作成
LightNode は Api の作成が超絶簡単です。適当に /api/Tests/ApiTest
で Hello World が返ってくるようにアクセスできるようにしてみましょう。
まずは、わかりやすくするために Apiフォルダ
を切って、Tests
クラスを作成します。
public class としてから、using に LightNode.Server
を追加し LightNodeContract
を継承してください。
using LightNode.Server; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LightNodeSelfHost.Api { public class Tests : LightNodeContract { } }
あとは、Hello World!!
を返す pubic メソッドを作成してみます。
public class Tests : LightNodeContract { /// <summary> /// Hellow World を返します。 /// </summary> /// <returns></returns> public string ApiTest() { return "Hello World!"; } }
Api を Swaggerでテストする
それでは、https://localhost:8080/Swagger/
にアクセスしてみてください。*2
無事に Swagger の画面が出たら成功です。
今回メソッドに属性を付けませんでしたが、[Post]
属性を指定するなど、アクセス制御も容易です。
では Swagger で実行してみてください。意図通り、Hello World!!
が返されればばっちりですね。
PowerShell での Apiアクセス
Swagger 素晴らしいです。ここまで確認できていれば、PowerShellでも確認も容易ですね。
wget https://localhost:8080/api/Tests/ApiTest -Method Post
もちろん[Post]属性をつけたので、 Get では拒否されます。
Post で意図通りに返ってきましたね。
現在、Content に BOM付でメッセージが返ってくるので残念な■が見えていますが、次回のバージョンで修正される予定のようなので待ちましょう。
LightNodeでのTips
いくつかのTips があります。順番にみていきましょう。
HTML でレンダリングされた Viewを返したい。
当初ありませんでしたが、現在は RazorEngine を使って .cshtml で書いて、ブラウザアクセスの時にHTML を返すことができます。
Startup.cs の Configurationで定義した通り、/pages
のアクセスには Viewを返してみましょう。
まずは、RazorEngine でレンダリングして、Htmlで返すための LightNodeContract として RazorContractBase.cs を作ります。
コードは次の通りで、RazorEngine のレンダリング結果を取っています。
ポイントは、Viewの cshtml を置く場所です。var viewPath = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Views", name);
で、VSのルート直下にある "Views"の中にある cshtml を対象にしています。
もし違うフォルダにcshtmlを置くなら、ここだけ変えましょう。
using System; using System.IO; using LightNode.Formatter; using LightNode.Server; using RazorEngine.Configuration; using RazorEngine.Templating; namespace LightNodeSelfHost { public abstract class RazorContractBase : LightNode.Server.LightNodeContract { static readonly IRazorEngineService razor = CreateRazorEngineService(); static IRazorEngineService CreateRazorEngineService() { var config = new TemplateServiceConfiguration(); config.DisableTempFileLocking = true; config.CachingProvider = new DefaultCachingProvider(_ => { }); config.TemplateManager = new DelegateTemplateManager(name => { // import from "Views" directory var viewPath = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Views", name); return System.IO.File.ReadAllText(viewPath); }); return RazorEngineService.Create(config); } protected string View(string viewName) { return View(viewName, new object()); } protected string View(string viewName, object model) { var type = model.GetType(); if (razor.IsTemplateCached(viewName, type)) { return razor.Run(viewName, type, model); } else { return razor.RunCompile(viewName, type, model); } } } }
合わせて、中に LightNodeが 上記を Htmlとして属性で指定できるようにしましょう。
public class Html : LightNode.Server.OperationOptionAttribute { public Html(AcceptVerbs acceptVerbs = AcceptVerbs.Get | AcceptVerbs.Post) : base(acceptVerbs, typeof(HtmlContentFormatterFactory)) { } }
残すは、先ほどのApiフォルダに Viewを返すApiを用意します。
先ほどと違うのは、継承するのが LightNodeContractBase ではなく、先ほど作ったRazorContractBase
であることです。
[Html]
属性をつけましょう。
[IgnoreClientGenerate]
属性は、LightNode.Client で生成対象外にしてくれるので便利!
RazorContractBaseで作った、Viewメソッドに、cshtml のファイル名を指定するだけというお手軽さです。
using LightNode.Server; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LightNodeSelfHost.Pages { /// <summary> /// View を返します。 /// </summary> class Views : RazorContractBase { [IgnoreClientGenerate] [Html] public string Index() { return this.View("Index.cshtml"); } } }
では、ルートのViewsフォルダに Nancyのころの cshtml があるのでちょちょっと触ります。
具体的には、以下のRazorセクションを足すだけです。
@using System.Linq; @using RazorEngine.Templating; @{ Layout = "Shared/_Layout.cshtml"; }
cshtmlの警告は、packages に Nugetでいれた、Microsoft.AspNet.WebPages にあるdllをぶちこめば消えます(雑
View も Api も用意したら、デバッグ実行してみてください。 https://localhost:8080/pages/Views/Index
にアクセスして、うまくViewが表示されれば ok ですね!
実装したApi の一覧を返したい
Apiの一覧は LightNode が一覧を持っており容易に返却可能です。LightNodeServerMiddleware.GetRegisteredHandlersInfo();
がそれにあたります。
例えば、こんな風に書けば Apiの一覧を返す Apiが書けます。
private static readonly string _root = "/api"; /// <summary> /// 実装されているAPIの一覧を返します。 /// </summary> /// <remarks>APIのUriを返します。</remarks> /// <returns></returns> [Post] public string[] ListApi() { var apis = LightNodeServerMiddleware.GetRegisteredHandlersInfo(); var key = apis.Select(x => x.Key).First(); return apis[key].SelectMany(x => x.RegisteredHandlers).Select(x => x.Key).ToArray(); }
Swagger でみてみましょう。
便利!ですね。Swagger を見ずともApiの一覧が取れるのはそれはそれで使いようがあるのです。もちろん Uri 以外にも様々な情報が取れますよ。LINQ 使って自由にいじってください。
LightNode で得られたメリット
いかがでしたでしょうか?Nancy でやっていた Api も View も LightNode で同様に実現できました。
ここまでの手間をかけて LightNode に移行してよかったの?と思われるかもしれませんが、圧倒的にやったかいがあります。
Nancy では Apiの追加のたびにルートを切ったりリクエスト処理が必要
LightNodeでは、Apiのルートが {ClassName}/{MethodName}
で固定です。わかりやすく自動的に行ってくれるのはApiの追加による負担を大幅に解消してくれます。
Modules がめんどくさいと思った人は私だけじゃないはず。。。!
Swagger によるApi実行環境の標準提供
Swagger がないともはややってられませんね!イヤホンと。Nancy に組み込むのも面倒なもので、標準プラグインとして提供されているのは相当素晴らしいです。
Glimpse 統合
SelfHost では使えませんが、IIS + Owin + LightNode なら、当然Glimpse が使えます。実行環境の測定は第一歩なので、できるの素晴らしい!
Production Ready
すでに謎社のインフラのデプロイ基盤は LightNode を使った環境に移行しています。そう、Production Ready なのです。
デプロイを極限まで高速化したい。そのための重要なパーツを LightNode は果たしています。
まとめ
書く書く詐欺マンでした!*3
これを気に、Nancy でつらい思いをしている人が、LightNode で楽になることを祈っています。