老A说的一句话让我很受启发,想要深刻了解框架,你要把精力聚焦在 架构设计的层面来思考问题。而透彻了解底层原理,最好的笨办法就是根据原理对框架核心进行 重建或者说 再造。看起来没有捷径,也是最快的捷径。
相信不少读者已经看过老A写的这篇文章《200行代码,7个对象——让你了解ASP.NET Core框架的本质》,这是一篇模仿和重建的典范。重建说白了就是模仿,模仿有一个前置条件就是你对底层原理要烂熟于心。不然画虎难画骨,本来要画虎,最后出来的是只猫。html
要理解原理就要去阅读源码,就像新人学开车,如何使用尚且磕磕碰碰,更况且让你去了解汽车的构造和引擎。编程
因此老A是引路人,我像个门外汉同样对前辈的文章解读不下5遍。我有几个疑问,1.为何是7个对象?2.这些对象如何分类,如何排序?3.这些对象发明的那个“无”是什么?服务器
在我深刻学习和解读的时候,我越加感受到老A的这篇文章很值得去深刻解读,所谓知其然,知其因此然,这样在编码过程才会游刃有余,如下开始我我的的解读。架构
public class Program { public static void Main() => new WebHostBuilder() .UseKestrel() .Configure(app => app.Use(context => context.Response.WriteAsync("Hello World!"))) .Build() .Run(); }
以上是原文的代码,咱们能够看到WebHostBuilder、Server(即Kestrel)、ApplicationBuilder(即app)三大重要的对象,以下图所示:app
WebHostBuilder这个父亲生出WebHost这个孩子,WebHost又生成整个ASP.NET Core最核心的内容,即由Server和中间件(Middleware)构成的管道Pipeline。咱们看下Pipeline的放大图:框架
继续把Pipeline拆开,有个很重要的ApplicationBuilder对象,里面包含Middleware、RequestDelegate。至于HttpContext是独立共享的对象,贯穿在整个管道中间,至此7大对象所有出场完毕。异步
Configure是个什么玩意?看下代码:async
public IWebHostBuilder Configure(Action<IApplicationBuilder> configure) { _configures.Add(configure); return this; }
咱们看到他是一个接受IApplicationBuilder的委托!继续刨根问底,IApplicationBuilder是什么玩意?看下源码:ide
public interface IApplicationBuilder { IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware); RequestDelegate Build(); }
他是一个注册中间件和生成Application的容器,那么Application是什么呢?源码没有这个对象,可是看代码(以下所示)咱们能够知道他是真正的委托执行者(Handler),执行是一个动做能够理解为app,我猜测这是取名为ApplicationBuilder的缘由。异步编程
public RequestDelegate Build() { _middlewares.Reverse(); return httpContext => { //_是一个有效的标识符,所以它能够用做参数名称 RequestDelegate next = _ => { _.Response.StatusCode = 404; return Task.CompletedTask; }; foreach (var middleware in _middlewares) { next = middleware(next); } return next(httpContext); }; }
更详细的过程能够参考下面这张图(图片来源),
WebHostBuilder开始Build的那一刻开始,WebHost被构造,Server被指定,Middlewares被指定,等WebHost真正启动的时候,Server开始监听,收到请求后,Middleware开始执行。
到此,一个完整的ASP.NET Core的流程就简单的走完了。接下来,咱们跟着老A一个一个对象的详细介绍。
这个对象应该是最容易理解的,也是咱们在编程时候遇到的最多的,最重要的(没有之一)对象。请看这个对象的简要代码:
public class HttpContext { public HttpRequest Request { get; } public HttpResponse Response { get; } } 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;} }
咱们知道一个Http事务包括最核心的Request(输入)和Response(输出),因此HttpContext包含这两个核心的东西。
老A建议你们从管道的角度来理解该对象的做用,管道和HTTP请求流程一脉相承。在Server接收到请求后,HttpContext被建立。
在服务器和中间件,中间件之间经过什么来传递信息?就是共享上下文,这个上下文就是HttpContext。能够说HttpContext是根据HTTP请求原理包裹的在管道之间的共享的一个上下文对象。
为何这里要把HttpContext放在第一个来介绍,由于这是一个最基础的对象。这7大对象的讲解顺序,我感受是从底层基础开始讲起,再层层往上,最后到WebHostBuilder。
这个委托过重要了,和HttpContext同样,老A建议你们从管道的角度来理解这个委托。咱们再复习一下管道的含义,如图所示:
这里的管道:Pipeline = Server + Middlewares
还能更简单一点吗?能够的:以下图所示
这里的管道:Pipeline =Server + HttpHandler。
多个Middlewares构成一个HttpHandler对象,这是整个管道的核心,那么应该如何用代码来表示呢?
老A讲到:“既然针对当前请求的全部输入和输出都经过HttpContext来表示,那么HttpHandler就能够表示成一个Action<HttpContext>对象”。
可是因为ASP.NET Core推崇异步编程,因此你应该想获得Task对象,那么HttpHandler天然就能够表示为一个Func<HttpContext,Task>对象。因为这个委托对象实在过重要了,因此咱们将它定义成一个独立的类型。下图展现的就是整个RequestDelegate的设计思路
public delegate Task RequestDelegate(HttpContext context);
这就是委托的由来!
委托是架构设计的底层技术,很是常见。由于委托能够承载约定的函数,遵循开闭原则,能很好的把扩展对外进行开放,保证了底层架构的稳定性。
这个对象比较费解。根据源码咱们知道Middleware也是一个委托对象(代码以下所示),中间件其实就是一个Func<RequestDelegate, RequestDelegate>对象:
private readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares = new List<Func<RequestDelegate, RequestDelegate>>();
该对象的输入和输入都是RequestDelegate,为何要这么设计呢?咱们想一下,当前中间件处理完成后须要将请求分发给后续中间件进行处理,他如何让后续的中间件参与当前的请求呢?因此他必需要拿到表明后续中间件管道构成的那个Handler。
以下图所示,也就是说,后续三个中间件构成的管道就是一个输入,执行完毕后,当前中间件也将被“融入”这个管道(此时该新管道就会由四个中间件构成的一个委托链),而后再输出给你由全部的中间件构成的新管道。以下图所示:
这又是一个builder,可见builder模式在ASP.NET Core有很是普遍的应用。可是该Builder构建的不是Application,到构建什么内容呢?从下面代码声明咱们能够看到他有两个功能。
从Use的使用来看,第一个功能是注册器,他把一个个中间件串联成一个管道。
public interface IApplicationBuilder { IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware); RequestDelegate Build(); }
第二个功能是Build,以下所示:
public class ApplicationBuilder : IApplicationBuilder { private readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares = new List<Func<RequestDelegate, RequestDelegate>>(); public RequestDelegate Build() { _middlewares.Reverse(); return httpContext => { RequestDelegate next = _ => { _.Response.StatusCode = 404; return Task.CompletedTask; }; foreach (var middleware in _middlewares) { next = middleware(next); } return next(httpContext); }; } public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware) { _middlewares.Add(middleware); return this; } }
Build真正作的事情是循环组装中间件,最后把组装好的委托链进行返回。从_middlewares.Reverse();咱们又能够知道,对于委托链来讲,中间件的注册顺序和执行顺序是相反的,这里须要进行反转,而后才能保证先注册的中间件先执行。
Server对象相对比较简单,咱们看下他的接口定义:
public interface IServer { Task StartAsync(RequestDelegate handler); }
咱们能够看到Server有个启动函数StartAsync,StartAsync内部封装了RequestDelegate中间件,同时内部也会new一个HttpContext(features),这样Server、RequestDelegate、HttpContext三者就所有聚齐了。
public class HttpListenerServer : IServer { private readonly HttpListener _httpListener; private readonly string[] _urls; public HttpListenerServer(params string[] urls) { _httpListener = new HttpListener(); //绑定默认监听地址(默认端口为5000) _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("Server started and is listening on: {0}", string.Join(';', _urls)); while (true) { //该方法将阻塞进程(这里使用了await),等待传入的请求,直到收到请求 var listenerContext = await _httpListener.GetContextAsync(); //打印状态行: 请求方法, URL, 协议版本 Console.WriteLine("{0} {1} HTTP/{2}", listenerContext.Request.HttpMethod, listenerContext.Request.RawUrl, listenerContext.Request.ProtocolVersion); // 获取抽象封装后的HttpListenerFeature var feature = new HttpListenerFeature(listenerContext); // 获取封装后的Feature集合 var features = new FeatureCollection() .Set<IHttpRequestFeature>(feature) .Set<IHttpResponseFeature>(feature); // 建立HttpContext var httpContext = new HttpContext(features); Console.WriteLine("[Info]: Server process one HTTP request start."); // 开始依次执行中间件 await handler(httpContext); Console.WriteLine("[Info]: Server process one HTTP request end."); // 关闭响应 listenerContext.Response.Close(); } } } public static partial class Extensions { public static IWebHostBuilder UseHttpListener(this IWebHostBuilder builder, params string[] urls) => builder.UseServer(new HttpListenerServer(urls));
经过以上代码分析,咱们能够画个图作总结:
因为ASP.NET Core能够支持不一样的WebServer,好比Kestrel和IIS,不一样的WebServer返回的HttpContext各不相同,因此这里又增长了一个中间层进行适配。这个中间层是什么呢?以下图所示,就是IRequestFeature和IResponseFeature。这一层是典型的适配器模式。
这里重点讲解的7大对象,这个适配器模式的实现细节暂且略过。
public interface IWebHost { Task StartAsync(); }
根据这段定义,咱们只能知道简单知道WebHost只要是用来启动什么对象用的,具体什么对象彷佛均可以。直到咱们看了实现,以下代码所示:
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() => _server.StartAsync(_handler); }
经过StartAsync,咱们知道WebHost是用来启动管道的中间件的,管道是在做为应用宿主的WebHost对象启动的时候被构建出来的。
而WebHost是如何被建立的呢?接下来就要讲他的父亲WebHostBuilder
public class WebHostBuilder : IWebHostBuilder { private IServer _server; private readonly List<Action<IApplicationBuilder>> _configures = new List<Action<IApplicationBuilder>>(); public IWebHostBuilder Configure(Action<IApplicationBuilder> configure) { _configures.Add(configure); return this; } public IWebHostBuilder UseServer(IServer server) { _server = server; return this; } public IWebHost Build() { var builder = new ApplicationBuilder(); foreach (var configure in _configures) { configure(builder); } return new WebHost(_server, builder.Build()); } }
咱们看到该对象有个Build方法,内部返回一个WebHost对象,这就是父亲的职责,负责生娃,他的娃就是WebHost。生出来的时候,给孩子一个ApplicationBuilder做为食物。而这个食物实际上是包裹起来的,展开来看就是一个个RequestDelegate委托链。
public interface IWebHostBuilder { IWebHostBuilder UseServer(IServer server); IWebHostBuilder Configure(Action<IApplicationBuilder> configure); IWebHost Build(); }
父亲除了建立WebHost以外,他还提供了注册服务器的UseServer方法和用来注册中间件的Configure方法。说到Configure方法,咱们必定还记得ApplicationBuilder方法的Use也是一个注册器。这两个注册器有何不一样呢?咱们对比一下代码:
public IWebHostBuilder Configure(Action<IApplicationBuilder> configure) { _configures.Add(configure); return this; }
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware) { _middlewares.Add(middleware); return this; }
其中Use只是增长一个中间件,Configure输入的是中间件构成的委托链。咱们看下入口函数的代码就知道了:
public static async Task Main() { await new WebHostBuilder() .UseHttpListener() .Configure(app => app .Use(FooMiddleware) .Use(BarMiddleware) .Use(BazMiddleware)) .Build() .StartAsync(); }