Artech 分享了 200行代碼,7個對象——讓你瞭解ASP.NET Core框架的本質 。 用一個極簡的模擬框架闡述了ASP.NET Core框架最爲核心的部分。html
這裏一步步來完成這個迷你框架。web
這段代碼很是簡單,啓動服務器並監聽本地5000端口和處理請求。服務器
static async Task Main(string[] args) { HttpListener httpListener = new HttpListener(); httpListener.Prefixes.Add("http://localhost:5000/"); httpListener.Start(); while (true) { var context = await httpListener.GetContextAsync(); await context.Response.OutputStream.WriteAsync(Encoding.UTF8.GetBytes("hello world")); context.Response.Close(); } }
如今要分離服務器(Server) 和 請求處理(handle),那麼一個簡單設計架構就出來了 :架構
Pipeline =Server + HttpHandlerapp
處理器要從請求(Request)中獲取數據,和定製響應(Response)的數據。
能夠想到咱們的處理器的處理方法應該是這樣的:框架
Task Handle(/*HttpRequest HttpResponse*/);
它能夠處理請求和響應,因爲處理能夠是同步或者異步的,因此返回Task。異步
很容易想到要封裝http請求和響應,封裝成一個上下文(Context) 供處理器使用(這樣的好處,處理器須要的其餘數據也能夠封裝在這裏,統一使用),因此要開始封裝HttpContext。async
public class HttpRequest { public Uri Url { get; } public NameValueCollection Headers { get; } public Stream Body { get; } } public class HttpResponse { public NameValueCollection Headers { get; } public Stream Body { get; } public int StatusCode { get; set; } } public class HttpContext { public HttpRequest Request { get; set; } public HttpResponse Response { get; set; } }
要支持不一樣的服務器,則不一樣的服務器都要提供HttpContext,這樣有了新的難題:服務器和HttpContext之間的適配 。
現階段的HttpContext包含HttpRequest和HttpResponse,請求和響應的數據都是要服務器(Server)提供的。
能夠定義接口,讓不一樣的服務器提供實現接口的實例:ide
public interface IHttpRequestFeature { Uri Url { get; } NameValueCollection Headers { get; } Stream Body { get; } } public interface IHttpResponseFeature { int StatusCode { get; set; } NameValueCollection Headers { get; } Stream Body { get; } }
爲了方便管理服務器和HttpContext之間的適配,定義一個功能的集合,經過類型能夠找到服務器提供的實例測試
public interface IFeatureCollection:IDictionary<Type,object> { } public static partial class Extensions { public static T Get<T>(this IFeatureCollection features) { return features.TryGetValue(typeof(T), out var value) ? (T)value : default; } public static IFeatureCollection Set<T>(this IFeatureCollection features,T feature) { features[typeof(T)] = feature; return features; } } public class FeatureCollection : Dictionary<Type, object>, IFeatureCollection { }
接下來修改HttpContext,完成適配
public class HttpContext { public HttpContext(IFeatureCollection features) { Request = new HttpRequest(features); Response = new HttpResponse(features); } public HttpRequest Request { get; set; } public HttpResponse Response { get; set; } } public class HttpRequest { private readonly IHttpRequestFeature _httpRequestFeature; public HttpRequest(IFeatureCollection features) { _httpRequestFeature = features.Get<IHttpRequestFeature>(); } public Uri Url => _httpRequestFeature.Url; public NameValueCollection Headers => _httpRequestFeature.Headers; public Stream Body => _httpRequestFeature.Body; } public class HttpResponse { private readonly IHttpResponseFeature _httpResponseFeature; public HttpResponse(IFeatureCollection features) { _httpResponseFeature = features.Get<IHttpResponseFeature>(); } public int StatusCode { get => _httpResponseFeature.StatusCode; set => _httpResponseFeature.StatusCode = value; } public NameValueCollection Headers => _httpResponseFeature.Headers; public Stream Body => _httpResponseFeature.Body; } public static partial class Extensions { public static Task WriteAsync(this HttpResponse response,string content) { var buffer = Encoding.UTF8.GetBytes(content); return response.Body.WriteAsync(buffer, 0, buffer.Length); } }
封裝好了HttpContext,終於能夠回過頭來看看處理器。
處理器的處理方法如今應該是這樣:
Task Handle(HttpContext context);
接下來就是怎麼定義這個處理器了。
起碼有兩種方式:
一、定義一個接口:
public interface IHttpHandler { Task Handle(HttpContext context); }
二、定義一個委託類型
public delegate Task RequestDelegate(HttpContext context);
兩種方式,本質上沒啥區別,委託代碼方式更靈活,不用實現一個接口,還符合鴨子模型。
處理器就選用委託類型。
定義了處理器,接下來看看服務器
服務器應該有一個開始方法,傳入處理器,並執行。
服務器抽象以下:
public interface IServer { Task StartAsync(RequestDelegate handler); }
定義一個HttpListener的服務器來實現IServer,因爲HttpListener的服務器須要提供HttpContext所需的數據,因此先定義HttpListenerFeature
public class HttpListenerFeature : IHttpRequestFeature, IHttpResponseFeature { private readonly HttpListenerContext _context; public HttpListenerFeature(HttpListenerContext context) => _context = context; Uri IHttpRequestFeature.Url => _context.Request.Url; NameValueCollection IHttpRequestFeature.Headers => _context.Request.Headers; NameValueCollection IHttpResponseFeature.Headers => _context.Response.Headers; Stream IHttpRequestFeature.Body => _context.Request.InputStream; Stream IHttpResponseFeature.Body => _context.Response.OutputStream; int IHttpResponseFeature.StatusCode { get => _context.Response.StatusCode; set => _context.Response.StatusCode = value; } }
定義HttpListener服務器
public class HttpListenerServer : IServer { private readonly HttpListener _httpListener; private readonly string[] _urls; public HttpListenerServer(params string[] urls) { _httpListener = new HttpListener(); _urls = urls.Any() ? urls : new string[] { "http://localhost:5000/" }; } public async Task StartAsync(RequestDelegate handler) { Array.ForEach(_urls, url => _httpListener.Prefixes.Add(url)); _httpListener.Start(); Console.WriteLine($"服務器{typeof(HttpListenerServer).Name} 開啓,開始監聽:{string.Join(";", _urls)}"); while (true) { var listtenerContext = await _httpListener.GetContextAsync(); var feature = new HttpListenerFeature(listtenerContext); var features = new FeatureCollection() .Set<IHttpRequestFeature>(feature) .Set<IHttpResponseFeature>(feature); var httpContext = new HttpContext(features); await handler(httpContext); listtenerContext.Response.Close(); } } }
修改Main方法運行測試
static async Task Main(string[] args) { IServer server = new HttpListenerServer(); async Task FooBar(HttpContext httpContext) { await httpContext.Response.WriteAsync("fooBar"); } await server.StartAsync(FooBar); }
運行結果以下:
至此,完成了服務器和處理器的抽象。
接下來單看處理器,全部的處理邏輯都集合在一個方法中,理想的方式是有多個處理器進行處理,好比處理器A處理完,則接着B處理器進行處理……
那麼就要管理多個處理器之間的鏈接方式。
假設有三個處理器A,B,C
框架要實現:A處理器開始處理,A處理完成以後,B處理器開始處理,B處理完成以後,C處理器開始處理。
引入中間件來完成處理器的鏈接。
中間件的要實現的功能很簡單:
//僞代碼 處理器 Middleware(傳入下一個要執行的處理器) { return 處理器 { //處理器的邏輯 下一個要執行的處理器在這裏執行 } }
舉個例子,如今有三個中間件FooMiddleware,BarMiddleware,BazMiddleware,分別對應的處理器爲A,B,C
要保證 處理器的處理順序爲 A->B->C
則先要執行 最後一個BazMiddleware,傳入「完成處理器」 返回 處理器C
而後把處理器C 傳入 BarMiddleware ,返回處理器B,依次類推。
//僞代碼 var middlewares=new []{FooMiddleware,BarMiddleware,BazMiddleware}; middlewares.Reverse(); var next=完成的處理器; foreach(var middleware in middlewares) { next= middleware(next); } //最後的next,就是最終要傳入IServer 中的處理器
模擬運行時的僞代碼:
//傳入完成處理器,返回處理器C 處理器 BazMiddleware(完成處理器) { return 處理器C { //處理器C的處理代碼 完成處理器 }; } //傳入處理器C,返回處理器B 處理器 BarMiddleware(處理器C) { return 處理器B { //處理器B的處理代碼 執行處理器C }; } //傳入處理器B,返回處理器A 處理器 FooMiddleware(處理器B) { return 處理器A { //處理器A的處理代碼 執行處理器B }; }
這樣當處理器A執行的時候,會先執行自身的代碼,而後執行處理器B,處理器B執行的時候,先執行自身的代碼,而後執行處理器C,依次類推。
因此,中間件的方法應該是下面這樣的:
RequestDelegate DoMiddleware(RequestDelegate next);
要管理中間件,就要提供註冊中間件的方法和最終構建出RequestDelegate的方法。
定義註冊中間件和構建處理器的接口: IApplicationBuilder
public interface IApplicationBuilder { IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware); RequestDelegate Build(); }
實現:
public class ApplicationBuilder : IApplicationBuilder { private readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares = new List<Func<RequestDelegate, RequestDelegate>>(); public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware) { _middlewares.Add(middleware); return this; } public RequestDelegate Build() { _middlewares.Reverse(); RequestDelegate next = context => { context.Response.StatusCode = 404; return Task.CompletedTask; }; foreach (var middleware in _middlewares) { next = middleware(next); } return next; } }
在Program 類裏定義三個中間件:
static RequestDelegate FooMiddleware(RequestDelegate next) { return async context => { await context.Response.WriteAsync("foo=>"); await next(context); }; } static RequestDelegate BarMiddleware(RequestDelegate next) { return async context => { await context.Response.WriteAsync("bar=>"); await next(context); }; } static RequestDelegate BazMiddleware(RequestDelegate next) { return async context => { await context.Response.WriteAsync("baz=>"); await next(context); }; }
修改Main方法測試運行
static async Task Main(string[] args) { IServer server = new HttpListenerServer(); var handler = new ApplicationBuilder() .Use(FooMiddleware) .Use(BarMiddleware) .Use(BazMiddleware) .Build(); await server.StartAsync(handler); }
運行結果以下:
爲了管理服務器和處理器之間的關係 抽象出web宿主
以下:
public interface IWebHost { Task StartAsync(); } public class WebHost : IWebHost { private readonly IServer _server; private readonly RequestDelegate _handler; public WebHost(IServer server,RequestDelegate handler) { _server = server; _handler = handler; } public Task StartAsync() { return _server.StartAsync(_handler); } }
Main方法能夠改一下測試
static async Task Main(string[] args) { IServer server = new HttpListenerServer(); var handler = new ApplicationBuilder() .Use(FooMiddleware) .Use(BarMiddleware) .Use(BazMiddleware) .Build(); IWebHost webHost = new WebHost(server, handler); await webHost.StartAsync(); }
要構建WebHost,須要知道用哪一個服務器,和配置了哪些中間件,最後能夠構建出WebHost
代碼以下:
public interface IWebHostBuilder { IWebHostBuilder UseServer(IServer server); IWebHostBuilder Configure(Action<IApplicationBuilder> configure); IWebHost Build(); } public class WebHostBuilder : IWebHostBuilder { private readonly List<Action<IApplicationBuilder>> _configures = new List<Action<IApplicationBuilder>>(); private IServer _server; public IWebHost Build() { //全部的中間件都註冊在builder上 var builder = new ApplicationBuilder(); foreach (var config in _configures) { config(builder); } return new WebHost(_server, builder.Build()); } public IWebHostBuilder Configure(Action<IApplicationBuilder> configure) { _configures.Add(configure); return this; } public IWebHostBuilder UseServer(IServer server) { _server = server; return this; } }
給IWebHostBuilder加一個擴展方法,用來使用HttpListenerServer 服務器
public static partial class Extensions { public static IWebHostBuilder UseHttpListener(this IWebHostBuilder builder, params string[] urls) { return builder.UseServer(new HttpListenerServer(urls)); } }
修改Mian方法
static async Task Main(string[] args) { await new WebHostBuilder() .UseHttpListener() .Configure(app=> app.Use(FooMiddleware) .Use(BarMiddleware) .Use(BazMiddleware)) .Build() .StartAsync(); }
完成。
public static IApplicationBuilder UseMiddleware(this IApplicationBuilder application, Type type) { //省略實現 } public static IApplicationBuilder UseMiddleware<T>(this IApplicationBuilder application) where T : class { return application.UseMiddleware(typeof(T)); }
添加一箇中間件
public class QuxMiddleware { private readonly RequestDelegate _next; public QuxMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { await context.Response.WriteAsync("qux=>"); await _next(context); } } public static partial class Extensions { public static IApplicationBuilder UseQux(this IApplicationBuilder builder) { return builder.UseMiddleware<QuxMiddleware>(); } }
使用中間件
class Program { static async Task Main(string[] args) { await new WebHostBuilder() .UseHttpListener() .Configure(app=> app.Use(FooMiddleware) .Use(BarMiddleware) .Use(BazMiddleware) .UseQux()) .Build() .StartAsync(); }
運行結果
最後,期待Artech 新書。