Your SlideShare is downloading. ×
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
How to Make Own Framework built on OWIN
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

How to Make Own Framework built on OWIN

22,875

Published on

2014/2/8 CLR/H

2014/2/8 CLR/H

Published in: Technology
0 Comments
10 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
22,875
On Slideshare
0
From Embeds
0
Number of Embeds
18
Actions
Shares
0
Downloads
15
Comments
0
Likes
10
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. How to Make Own Framework built on OWIN A Case of LightNode 2014/02/08 Yoshifumi Kawai - @neuecc
  • 2. Self Introduction @仕事 株式会社グラニ 取締役CTO C# 5.0 + .NET Framework 4.5 + ASP.NET MVC 5 最先端C#によるハイパフォーマンスWebアプリケーション @個人活動 Microsoft MVP for Visual C# Web http://neue.cc/ Twitter @neuecc LINQがひじょーに好き、趣味はライブラリ作成
  • 3. Public Libraries NuGet公開パッケージ数30突破 https://www.nuget.org/profiles/neuecc/ linq.js JavaScriptにLINQ to Objectsを移植したライブラリ http://linqjs.codeplex.com/ その他色々 Chaining Assertion(ユニットテスト補助), DynamicJson(dynamicなJSON), AsyncOAuth(PCLのOAuthライブラリ), CloudStructures(Object/Redisマッパー), ReactiveProperty(Rx + MVVM), AnonymousComparer(LINQ補助), etc... 最近はOwinにムチューなのでMiddlewareいろいろ作ってます
  • 4. Grani C#を使ってソーシャルゲームを開発している会社 C# 5.0 + ASP.NET MVC 5 + TypeScript + ... かなり規模は大きめ Over 100 Servers, 10000req/s サーバーはクラウド(AWS)に展開 AWS上でWindows Server 2012 データベースはcr1.8xlarge(32コア244GBメモリ)を複数台 KVSとしてRedisを多用
  • 5. Agenda
  • 6. Agenda OWINについて OWINとは何か、どういう仕組みなのかを理解する OWINフレームワーク(LightNode)の実装解説 Webフレームワークはどう動いているのかを、LightNodeとい う非常に小さな実装を通して把握する 同時に実装のしかた、性能の出しかたを理解する OWINの意義 それらを通してASP.NETの未来を考える
  • 7. OWIN Open Web Interface for .NET
  • 8. いままでのASP.NET System.WebはIISと密接に結びついてた IIS以外で動かすことができない で、それの何が悪いの?何も悪くない!!! 時は流れて現代 SignalRをセルフホスト!軽量ユニットテスト!など用途の広がり そろそろアプリケーションとサーバーを引っぺがす時が来た そこで現れたのが、OWIN ついでにSystem.Webのレガシー設計をぶち壊す一世一代のチャンス
  • 9. OWIN is... 仕様 詳しくは後で。 Perl/PSGI, Python/WSGI, Ruby/Rack, Node.js/Connectなどの影響 よってOwinについて知りたい時はPerlのPlack Handbook見るのが最も良い https://github.com/miyagawa/plack-handbook/tree/master/ja 二つの利点 バックエンドを自由に選べる(IIS,HttpListner,etc...) 実質的にIISしか使わない?ユニットテストとかにも便利ですよ 自由に組み合わせられるミドルウェアパイプライン シンプルながらも非常に強力、そして単純だから分かりやすく作りやすい
  • 10. Host:Server
  • 11. Owin Image "Onion" Request Response Web Framework Middleware Web Application Server Host
  • 12. Owin Image "Onion" Request Response Web Framework Middleware Web Application Server Host HostとServerは等しいことがある FramworkとMiddlewareの間に、実装上は特に違 いはない。アプリケーションにとって直接のレイ ヤーを指す(よってPipelineの中央になる)
  • 13. Owin:Server Request Response IIS HttpListner UnitTestMock etc...
  • 14. Owin:Server Request Response IIS HttpListner UnitTestMock etc... Helios(Microsoft.Owin.Host.IIS) 従来のIISパイプラインを通さず、直接叩くこ とにより超性能を叩きだした実装(まだα)
  • 15. System.Web:HttpContext Request Response HttpContext 今まではHttpContextに情報が詰め込まれて やってきてたのが大前提だった この状態はIISに激しく依存している
  • 16. OWIN:Environment Request Response IDictionary<string, object> Environment(環境変数)として完全に動的に 情報が送られるので依存がない ただし必須のKeyは幾つか存在する
  • 17. Context vs Environment Request Response IIS HttpListner UnitTestMock etc... // System.Web var ua1 = HttpContext.Current.Request.UserAgent; // OWIN (Environment = IDictionary<string, object>) var headers = environment["owin.RequestHeaders"] as IDictionary<string, string[]>; var ua2 = headers["User-Agent"]; // System.Web var outStream = HttpContext.Current.Response.Outpu // OWIN (Environment = IDictionary<string, object> var stream = environment["owin.ResponseBody"] as S IDictionary<string, object>
  • 18. 辞書 is ダルい そこでKatanaのOwinContext EnvironmentをラップしたOwinContext 生でCookie扱うとか超絶ダルい(文字列を気合パース)けどその辺も楽 Katana vs Owin Owin自体はシンプルな仕様 Environmentと後述するAppFuncの定義だけ KatanaはMicrosoftによるリファレンス実装 実装は複数あってもよく、そのうちの一つに過ぎない (というのが建前、実質的には一択になると思います) OwinContextのほか、便利MiddlewareやMiddlewareベースなど色々 使っても使わなくてもいい、けれど、使ったほうが楽(でも依存性が?) // Katana(Microsoft.Owin)/IOwinContext 色々生えてて便利 var body = context.Request.Body; var cookie = context.Request.Cookies["hogehoeg"];
  • 19. Middleware Pipeline
  • 20. Before:HttpApplication Pipeline BeginRequest イベントを発生します。 AuthenticateRequest イベントを発生します。 PostAuthenticateRequest イベントを発生します。 AuthorizeRequest イベントを発生します。 PostAuthorizeRequest イベントを発生します。 ResolveRequestCache イベントを発生します。 PostResolveRequestCache イベントを発生します。 MapRequestHandler イベントを発生します。 PostMapRequestHandler イベントを発生します。 AcquireRequestState イベントを発生します。 PostAcquireRequestState イベントを発生します。 PreRequestHandlerExecute イベントを発生します。 要求に対応する IHttpHandler クラスの ProcessRequest メソッド (または非 同期バージョンの IHttpAsyncHandler.BeginProcessRequest) を呼び出します。 PostRequestHandlerExecute イベントを発生します。 ReleaseRequestState イベントを発生します。 PostReleaseRequestState イベントを発生します。 UpdateRequestCache イベントを発生します。 PostUpdateRequestCache イベントを発生します。 LogRequest イベントを発生します。
  • 21. After:Owin Middleware Pipeline Request Response Framework Middleware
  • 22. AppFunc Request Response Framework Middleware Func<IDictionary<string, object>, Task> により次のミドルウェアに進む 環境変数を受け取り、実行完了を非同期で返 す。このFuncをAppFuncと定義する using AppFunc = System.Func<IDictionary<string, object>, Task>;
  • 23. Pipeline - Code Image Framework Middleware // Middlewareのコードイメージ try { // 実行前アクション(外側から円の中央へ向かう) // next = AppFunc = Func<IDictionary<string, object>, Task> await next(env); // 一つ円の内側へ // 実行後アクション(円の中央から外側へ向かう) } catch { // 例外時アクション } finally { // Middleware終了時アクション }
  • 24. Stack the Middleware! Framework Middleware // 未指定の場合はStartupクラスを自動的に探す [assembly: OwinStartup(typeof(Hello.Startup))] namespace Hello { // アプリケーション立ち上げ時に一度だけ呼ばれる // (Global.asaxのApplication_Startみたいなもの) public class Startup { public void Configuration(IAppBuilder app) { // 利用するMiddlewareを重ねていく app.UseRequestScopeContext() .UseRedisSession() .UseTwitterAuthentication() .UseLightNode(); } } }
  • 25. LightNode .NET’s Fastest REST Framework
  • 26. LightNodeとは Micro RPC/REST Framework http://neuecc.github.io/LightNode/ ASP.NET Web APIとかと同種の代物 超絶Lightweight、超簡単にサーバーAPIが作れる パフォーマンスも超絶凄い、.NET最速 クライアントサイドのコード自動生成機能付き OWIN上で構築されている 次世代!
  • 27. Why use LightNode クライアントサイド作る苦行 Web APIでRESTサーバー実装した、ではクライアント側は? URLを、リソースを、手作業で特化クライアントSDK作ることに なる。超絶ダルいし、開発途中はサーバー側のAPIもどんどん変 わっていく 自動生成すればいい 自動生成といってもSOAP/WCFのようなヘヴィなものは嫌 けれどクライアントSDKを前提に置くならRPC風は具合がいい 軽量なサーバー実装、軽量で変更耐性の高い自動生成
  • 28. Demo... Create API Server and Upload to Azure Web Sites in 10 Minutes
  • 29. Lightweight Server and Client // http://hogehoge/Hello/GetMC?name={0}&x={1}&y={2}&e={3} // と、呼べるサーバーAPIが公開される public class Hello : LightNode.Server.LightNodeContract { public MyClass GetMC(string name, int x, int y, MyEnum e) { return new MyClass { Name = name, Sum = (x + y) * (int)e }; } } // こう呼べるクライアントが自動生成される var client = new LightNodeClient("http://hogehoge/") var mc = await client.Hello.GetMCAsync("hoge", 20, 20, MyEnum.B); メソッドを作れば、それがそのまま APIとして公開され、戻り値は (JSONなどに)シリアライズされる T4による自動生成、APIのDLLを指定する ことにより、それを解析してHttpClient ベースの専用クライアントを生成する
  • 30. Fastest! フレームワークでは最速! これより速いのは生ハンドラのみ
  • 31. Inside LightNode ver 0.2.0~0.3.0
  • 32. Framework Pipeline SelectHandler Execute WriteResponse どのメソッドを実行するか探す メソッドにパラメータを割り当てる 結果をResponseStreamに書く ParameterBinding メソッドを実行する
  • 33. Framework Pipeline SelectHandler Execute WriteResponse どのメソッドを実行するか探す ParameterBinding
  • 34. RegsiterHandler Select...の前に実行ハンドラを予め登録 アセンブリ中にあるLightNodeContractを継承したクラスの全パ ブリックメソッドが実行ハンドラというルール 最初に一回だけ登録すればいいのでOwinのStartup時に行う 先にできるものは先に全部やったほうが実行性能的にもいい public class Hello : LightNodeContract { public string Say() { return "Hello LightNode"; } }
  • 35. RegsiterHandler LightNodeServer.cs | LightNodeServer.RegisterHandler // Assembly[] hostAssemblies // デフォルトはAppDomain.CurrentDomain.GetAssemblies()から var contractTypes = hostAssemblies .SelectMany(x => x.GetTypes()) .Where(x => typeof(LightNodeContract).IsAssignableFrom(x)) .Where(x => !x.IsAbstract); // cache field readonly Dictionary<RequestPath, OperationHandler> handlers = new Dictionary<RequestPath, OperationHandler>(); // --- snip --- // create handler var handler = new OperationHandler(options, classType, methodInfo); lock (handlers) { var path = new RequestPath(className, methodName); // dictionary key handlers.Add(path, handler); // add handler cache } アセンブリを舐めて対 象クラスを探しだす ハンドラを生成して 辞書にキャッシュ
  • 36. Create Delegate MethodInfoのInvokeは遅い 最も簡単な動的な実行だけど結果は今ひとつ 動的実行を高速化するにはDelegateを作ってキャッシュする キャッシュのための共通化 Dictionaryに入れるため全メソッドを一つの型に統一する 全てのメソッドはobject[]を受け取りobjectを返すと表現可能 つまりFunc<object[], object> (実際はT,void,Task,Task<T>の4つの分別はあり) OperationHandler.cs | OperationHandler.ctor
  • 37. Expression Compile // common prepare var args = Expression.Parameter(typeof(object[]), "args"); var parameters = methodInfo.GetParameters() .Select((x, i) => Expression.Convert( Expression.ArrayIndex(args, Expression.Constant(i)), x.ParameterType)) .ToArray(); //--- snip --- // (object[] args) => (object)new X().M((T1)args[0], (T2)args[1])... var lambda = Expression.Lambda<Func<IDictionary<string, object>, object[], object>>( Expression.Convert( Expression.Call( Expression.MemberInit(Expression.New(classType), envBind), methodInfo, parameters) , typeof(object)), envArg, args); this.handlerBodyType = HandlerBodyType.Func; this.methodFuncBody = lambda.Compile(); // Compile! 実際はOwinのEnvironment を引数に受け取る仕様 このデリゲートをキャッ シュするんだよもん その他、Task対応の処理は更に面倒 だったりするので詳しくはブログで:) http://neue.cc/2014/01/27_446.html
  • 38. Select Handler ルーティングのない素敵 URLのルールは /ClassName/MethodName で固定 なので単純にSplitして辞書から持ってくるだけでOK 最速 これより高速は無理 LightNodeServer.cs | LightNodeServer.SelectHandler var path = environment["owin.RequestPath"] as string; var keyBase = path.Trim('/').Split('/'); // {ClassName, MethodName} var key = new RequestPath(keyBase[0], keyBase[1]); // キャッシュ済みハンドラを辞書からルックアップ OperationHandler handler; if (handlers.TryGetValue(key, out handler)) { return handler; }
  • 39. Framework Pipeline SelectHandler Execute WriteResponse メソッドにパラメータを割り当てるParameterBinding
  • 40. ParameterBinding is... ValueProvider (Parameter -> string) public int Add(int x, int y = 0) ParameterBinder (-> object[]) TypeBinder (string -> T)hoge?x=10&y=20
  • 41. ValueProvider リクエストからModelBinderに渡す文字列を作る ?x=10&y=20だったら{x:”10”, y:”20”}を作る ValueProvider.cs | ValueProvider.ctor var queryString = environment["owin.RequestQueryString"] as string; AppendValues(queryString); if (verb != AcceptVerbs.Get) { using (var sr = new StreamReader((environment["owin.RequestBody"] as Stream) { var formUrlEncoded = sr.ReadToEnd(); AppendValues(formUrlEncoded); } } QueryStringと FormUrlEncodedのみ対応
  • 42. TypeBinder 単純にTryParseするだけ クソ単純 = わかりやすい = 速い = 正義 TypeBinder.cs | TypeBinder.cctor public delegate bool TryParse(string x, out object result); static readonly Dictionary<Type, TryParse> convertTypeDictionary = new Dictionary<Type, TryParse>(33) { {typeof(Boolean) ,(string x, out object result) => { Boolean @out; var success = Boolean.TryParse(x, out @out); result = (object)@out; return success; }}, {typeof(Nullable<Boolean>),(string x, out object result) => { Boolean @out; result = Boolean.TryParse(x, out @out) ? (object)@out : null; return true; }}, {typeof(Int32) ,(string x, out object result) => { Int32 @out; var success = Int32.TryParse(x, out @out); result = (object)@out; return success; }}, {typeof(Nullable<Int32>),(string x, out object result) => { Int32 @out; result = Int32.TryParse(x, out @out)? (object)@out:null; return true; }}, // snip.... } out付きは汎用デリゲート にないので手作りする
  • 43. ParameterBinder var conv = TypeBinder.GetConverter(item.ParameterType, !options.ParameterEnumAllowsFieldNameParse); object pValue; if (conv(value ?? values[0], out pValue)) { methodParameters[i] = pValue; continue; } else if (item.IsOptional) { methodParameters[i] = item.DefaultValue; continue; } else if ((!item.ParameterTypeIsString || options.ParameterStringImplicitNullAsDefault) && (item.ParameterTypeIsClass || item.ParameterTypeIsNullable) { methodParameters[i] = null; continue; }ParameterBinder.cs | ParameterBinder.BindParameter TypeBinderの結果からパース できたら採用。できなかった らオプション引数やnullが可 能かチェックしてobject[] を 作る
  • 44. Framework Pipeline SelectHandler Execute WriteResponse ParameterBinding メソッドを実行する
  • 45. with Filter OperationHandler.cs | OperationHandler.Execute public Task Execute(LightNodeOptions options, OperationContext context) { int index = -1; Func<Task> invokeRecursive = null; invokeRecursive = () => { index += 1; if (filters.Length != index) { // chain next filter return filters[index].Invoke(context, invokeRecursive); } else { // execute operation return ExecuteOperation(options, context); } }; return invokeRecursive(); } この中にobject[]が入ってる フィルタ適応もOwinのパイ プライン風になっていて再 起で適用していく
  • 46. Framework Pipeline SelectHandler Execute WriteResponse 結果をResponseStreamに書く ParameterBinding
  • 47. Write Stream var responseStream = environment["owin.ResponseBody"] as Stream; if (options.StreamWriteOption == StreamWriteOption.DirectWrite) { context.ContentFormatter.Serialize(new UnclosableStream(responseStream), result); } else { using (var buffer = new MemoryStream()) { context.ContentFormatter.Serialize(new UnclosableStream(buffer), result); responseHeader["Content-Length"] = new[] { buffer.Position.ToString() }; buffer.Position = 0; if (options.StreamWriteOption == StreamWriteOption.BufferAndWrite) { buffer.CopyTo(responseStream); // not CopyToAsync } else { await buffer.CopyToAsync(responseStream).ConfigureAwait(false); } } } UnclosableStreamにラップ(Close/Dispose が呼ばれても何もしないStream) OperationHandler.cs | OperationHandler.ExecuteOperation MemoryStreamに書き出してから出力(デフォルト) 理由: 1. Content-Lengthの取得 2. Streamにレスポンスを書き出している途中にシリアラ イザが落ちたりすると、500に変更したくてもできな い (Bodyが1byteでも吐かれた後はStatusCodeは変更 できない)
  • 48. Integrated with OWIN
  • 49. using AppFunc = Func<IDictionary<string, object>, Task>; public class LightNodeServerMiddleware { readonly LightNodeServer engine; readonly bool useOtherMiddleware; readonly AppFunc next; public LightNodeServerMiddleware(AppFunc next, LightNodeOptions options, Assembly[] hostAssemblies) { this.next = next; this.useOtherMiddleware = options.UseOtherMiddleware; this.engine = new LightNodeServer(options); this.engine.RegisterHandler(hostAssemblies); } public async Task Invoke(IDictionary<string, object> environment) { if (useOtherMiddleware) { await engine.ProcessRequest(environment).ConfigureAwait(true); await next(environment).ConfigureAwait(false); } else { await engine.ProcessRequest(environment).ConfigureAwait(false); } } } Middleware 何も継承しない(Katanaを使わない場合) フレームワークとして作る/使う場合、nextの Invokeはしない(終点) LightNodeもデフォルトはこちらのパスに来る nextはユーザーが明示的に後続をUseしなかっ た時、Katanaの場合はStatusCode = 404を埋め るAppFuncが詰まっている(よって不要なら呼 んではならない) nextはコンストラクタに乗ってくる(決め打ち)next/environment引数は動的に決め打たれてる
  • 50. UseExtensions namespace Owin { public static class AppBuilderLightNodeMiddlewareExtensions { public static IAppBuilder UseLightNode(this IAppBuilder app) { return UseLightNode(app, new LightNodeOptions(AcceptVerbs.Get | AcceptVerbs.Post, new LightNode.Formatter.JavaScriptContentFormatter())); } public static IAppBuilder UseLightNode(this IAppBuilder app, LightNodeOptions options) { return app.Use(typeof(LightNodeServerMiddleware), options); } public static IAppBuilder UseLightNode(this IAppBuilder app, LightNodeOptions options, pa { return app.Use(typeof(LightNodeServerMiddleware), options, hostAssemblies); } } StartupのConfigurationで app.UseLightNode() とだけ書いて有効にできるようにnamespace Owin
  • 51. Conclusion
  • 52. Create Your Own Framework 自分達に最適なものを作り、組み立てる 小さなユーティリティから大きいフレームワークまで 既成品だけでは全てのシチュエーションに合うわけではない Middleware Stack ルーティング,キャッシュ,認証などは他のMiddlewareに任せる 俺々フレームワークの欠点である、何でも自作する必要がある、 他の良いものが使えない、という欠点がなくなる
  • 53. Close the world OWINはYOU!を作り出す環境 使うだけの時代にさよなら、作り出す時代の幕開け YOU! YOU!
  • 54. Open the nExt Microsoftだけがリードする時代ではない Owin自体もコミュニティ発祥 最先端は自分達が、共に築き上げていく 新しい時代の、新しいフレームワーク LightNodeはそれを具現化する http://neuecc.github.io/LightNode/ 是非、試してみてください。

×