老 A 说的一句话让我很受启发, 想要深入了解框架, 你要把精力聚焦在架构设计的层面来思考问题. 而透彻了解底层原理, 最好的笨办法就是根据原理对框架核心进行重建或者说再造. 看起来没有捷径, 也是最快的捷径.
题外话
相信很多读者已经看过老 A 写的这篇文章《200 行代码, 7 个对象 -- 让你了解 ASP.NET Core 框架的本质》, 这是一篇模仿和重建的典范. 重建说白了就是模仿, 模仿有一个前置条件就是你对底层原理要烂熟于心. 否则画虎难画骨, 原本要画虎, 最后出来的是只猫.
要理解原理就要去阅读源码, 就像新人学开车, 如何使用尚且磕磕碰碰, 更何况让你去了解汽车的构造和引擎.
所以老 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)三大重要的对象, 如下图所示:
WebHostBuilder 这个父亲生出 WebHost 这个孩子, WebHost 又生成整个 ASP.NET Core 最核心的内容, 即由 Server 和中间件 (Middleware) 构成的管道 Pipeline. 我们看下 Pipeline 的放大图:
继续把 Pipeline 拆开, 有个很重要的 ApplicationBuilder 对象, 里面包含 Middleware,RequestDelegate. 至于 HttpContext 是独立共享的对象, 贯穿在整个管道中间, 至此 7 大对象全部出场完毕.
Configure 是个什么玩意? 看下代码:
- public IWebHostBuilder Configure(Action<IApplicationBuilder> configure)
- {
- _configures.Add(configure);
- return this;
- }
我们看到他是一个接受 IApplicationBuilder 的委托! 继续刨根问底, IApplicationBuilder 是什么玩意? 看下源码:
- 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 一个一个对象的详细介绍.
7 个对象解读
1.HttpContext
这个对象应该是最容易理解的, 也是我们在编程时候遇到的最多的, 最重要的 (没有之一) 对象. 请看这个对象的简要代码:
- 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.
2.RequestDelegate
这个委托太重要了, 和 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);
这就是委托的由来!
为什么是委托, 而不是别的函数?
委托是架构设计的底层技术, 非常常见. 因为委托可以承载约定的函数, 遵循开闭原则, 能很好的把扩展对外进行开放, 保证了底层架构的稳定性.
3.Middleware
这个对象比较费解. 根据源码我们知道 Middleware 也是一个委托对象(代码如下所示), 中间件其实就是一个 Func<RequestDelegate,RequestDelegate > 对象:
private readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares = new List<Func<RequestDelegate, RequestDelegate>>();
该对象的输入和输入都是 RequestDelegate, 为什么要这么设计呢? 我们想一下, 当前中间件处理完成后需要将请求分发给后续中间件进行处理, 他如何让后续的中间件参与当前的请求呢? 所以他必须要拿到代表后续中间件管道构成的那个 Handler.
如下图所示, 也就是说, 后续三个中间件构成的管道就是一个输入, 执行完毕后, 当前中间件也将被 "融入" 这个管道(此时该新管道就会由四个中间件构成的一个委托链), 然后再输出给你由所有的中间件构成的新管道. 如下图所示:
4.ApplicationBuilder
这又是一个 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(); 我们又可以知道, 对于委托链来说, 中间件的注册顺序和执行顺序是相反的, 这里需要进行反转, 然后才能保证先注册的中间件先执行.
5.Server
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 大对象, 这个适配器模式的实现细节暂且略过.
- 6.WebHost
- 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
- 7.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 也是一个注册器. 这两个注册器有何不同呢? 我们对比一下代码:
- WebHostBuilder:
- public IWebHostBuilder Configure(Action<IApplicationBuilder> configure)
- {
- _configures.Add(configure);
- return this;
- }
- ApplicationBuilder:
- 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();
- }
参加文章:
深入研究 Mini ASP.NET Core
一个 Mini 的 ASP.NET Core 框架的实现
200 行代码, 7 个对象 -- 让你了解 ASP.NET Core 框架的本质
来源: https://www.cnblogs.com/jackyfei/p/10838586.html