一个迷你ASP.NET Core框架的实现(下)

【框架内幕作者 / Edison Zhou

这是恰童鞋骚年的第196篇原创文章


上一篇我们了解了AspNetCore.Mini这个项目的背景及项目结构和流程,这一篇我们继续解析几个核心对象。本文整理自A大(蒋金楠)的主题分享,点击本文底部“阅读原文”可以观看A大的该主题视频分享。

1基于HttpListener的WebServer

刚刚在WebHost中注入了Server,并启动了Server。那么,这个Server长啥样呢?我们知道,在ASP.NET Core中封装了Kestrel和IIS两个Server供我们使用,那么它们肯定有一个抽象层(这里是接口),定义了他们共有的行为,这里我们也写一个IServer:

public interface IServer
{Task RunAsync(RequestDelegate handler);
}

IServer接口行为很简单,就是约定一个启动的方法RunAsync,接受参数是中间件(本质就是一个请求处理的委托)。

有了IServer接口,就可以基于IServer封装基于不同平台的WebServer了,这里基于HttpListener实现了一个HttpListenerServer如下(HttpListener简化了Http协议的监听,仅需通过字符串的方法提供监听的地址和端口号以及虚拟路径,就可以开始监听请求):

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 RunAsync(RequestDelegate handler){Array.ForEach(_urls, url => _httpListener.Prefixes.Add(url));if (!_httpListener.IsListening){// 启动HttpListener_httpListener.Start();}Console.WriteLine("[Info]: 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);// 获取抽象封装后的HttpListenerFeaturevar feature = new HttpListenerFeature(listenerContext);// 获取封装后的Feature集合var features = new FeatureCollection().Set<IHttpRequestFeature>(feature).Set<IHttpResponseFeature>(feature);// 创建HttpContextvar 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();}}
}/// <summary>
/// IWebHostBuilder扩展:使用基于HttpListener的Server
/// </summary>
public static partial class Extensions
{public static IWebHostBuilder UseHttpListener(this IWebHostBuilder builder, params string[] urls)=> builder.UseServer(new HttpListenerServer(urls));
}

有了Server,也有了中间件,我们要进行处理的上下文在哪里?熟悉ASP.NET请求处理的童鞋都知道,我们会操作一个叫做HttpContext的东西,它包裹了一个HttpRequest和一个HttpResponse,我们要进行的处理操作就是拿到HttpRequest里面的各种参数进行处理,然后将返回的结果包裹或调用HttpResponse的某些方法进行响应返回。在ASP.NET Core Mini中,也不例外,我们会创建一个HttpContext,然后将这个HttpContext传递给注册的中间件,各个中间件也可以拿到这个HttpContext去做具体的处理了。但是,不同的Server和单一的HttpContext之间需要如何适配呢?因为我们可以注册多样的Server,可以是IIS也可以是Kestrel还可以是这里的HttpListenerServer

这时候,我们又可以提取一个抽象层了,如上图所示,底层是具体的基于不同平台技术的Server,上层是HttpContext共享上下文,中间层是一个抽象层,它是基于不同Server抽象出来的接口,本质是不同Server的适配器,下面就是这个IFeature的定义:

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; }
}

这里不再解释,下面来看看HttpListener的适配的实现:

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 { return _context.Response.StatusCode; }set { _context.Response.StatusCode = value; }}
}

可以看出,这是一个典型的适配器模式的应用,通过一个抽象层接口,为不同Server提供HttpRequest和HttpResponse对象的核心属性。

2Middleware与ApplicationBuilder

在启动项目中,定义了三个中间件如下所示:

public static RequestDelegate FooMiddleware(RequestDelegate next)
=> async context =>
{await context.Response.WriteAsync("Foo=>");await next(context);
};public static RequestDelegate BarMiddleware(RequestDelegate next)
=> async context =>
{await context.Response.WriteAsync("Bar=>");await next(context);
};public static RequestDelegate BazMiddleware(RequestDelegate next)
=> context => context.Response.WriteAsync("Baz");

可以看到,每个中间件的作用都很简单,就是向响应流中输出一个字符串。其中Foo和Bar两个中间件在输出之后,还会调用下一个中间件进行处理,而Baz不会调用下一个中间件进行处理,因此Baz在注册顺序上排在了最后,这也解释了我们为何在ASP.NET Core中进行中间件的注册时,注册的顺序比较讲究,因为这会影响到后面的执行顺序。

刚刚在进行WebHost的创建时,调用了WebHostBuilder的Configure方法进行中间件的注册,而这个Configure方法的输入参数是一个IApplicationBuilder的委托:

public interface IApplicationBuilder
{IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);RequestDelegate Build();
}

可能直接看这个接口定义不是太明白,下面来看看ApplicationBuilder的实现:

public class ApplicationBuilder : IApplicationBuilder
{private readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares = new List<Func<RequestDelegate, RequestDelegate>>();/// <summary>/// 构建请求处理管道/// </summary>/// <returns>RequestDelegate</returns>public RequestDelegate Build(){_middlewares.Reverse(); // 倒置注册中间件集合的顺序return httpContext =>{// 注册默认中间件 => 返回404响应RequestDelegate next = _ => {_.Response.StatusCode = 404;return Task.CompletedTask;};// 构建中间件处理管道foreach (var middleware in _middlewares){next = middleware(next);}return next(httpContext);};}/// <summary>/// 注册中间件/// </summary>/// <param name="middleware">中间件</param>/// <returns>ApplicationBuilder</returns>public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware){_middlewares.Add(middleware);return this;}
}

其中,Use方法的作用就是接受中间件进行注册,Build方法的作用就是构建由注册中间件组成的请求处理管道,而Server加上这个由中间件组成的请求处理管道便是ASP.NET Core的核心内容。因此,我们可以说ASP.NET Core Pipeline = Server + Middlewares。此外,我们还可以将多个Middleware构建成一个单一的“HttpHandler”,那么整个ASP.NET Core框架将具有更加简单的表达:Pipeline = Server + HttpHandler

因此,这里的Build方法中做了以下几件事情:

(1)倒置注册中间件集合的顺序

_middlewares.Reverse();

为什么要倒置顺序呢?不是说执行顺序要跟注册顺序保持一致么?别急,且看后面的代码。

(2)注册默认中间件

return httpContext =>
{// 注册默认中间件 => 返回404响应RequestDelegate next = _ => {_.Response.StatusCode = 404;return Task.CompletedTask;};......
}

这里默认中间件是返回404,在如果没有手动注册任何中间件的情况下生效。

(3)构建一个中间件处理管道 => "HttpHandler"

public RequestDelegate Build()
{......return httpContext =>{......// 构建中间件处理管道foreach (var middleware in _middlewares){next = middleware(next);}return next(httpContext);};
}

在通过Use方法注册多个中间件到middlewares集合中后,会在这里通过一个遍历组成一个单一的middleware(在这里表示为一个RequestDelegate对象),如下图所示。

对于middleware,它在这里是一个Func<RequestDeletegate, RequestDelegate>对象,它的输入和输出都是RequestDelegate。

对于管道的中的某一个middleware来说,由后续middleware组成的管道体现为一个RequestDelegate对象,由于当前middleware在完成了自身的请求处理任务之后,往往需要将请求分发给后续middleware进行处理,所以它需要将由后续中间件构成的RequestDelegate作为输入。当代表中间件的委托对象执行之后,我们希望的是将当前中间件“纳入”这个管道,那么新的管道体现的RequestDelegate自然成为了输出结果

因此,这里也就解释了为什么要在第一步中进行middleware的顺序的倒置,否则无法以注册的顺序构成一个单一的middleware,下图是示例代码中的所有middleware构成的一个单一的RequestDelegate,经过层层包裹,以达到依次执行各个middleware的效果。需要注意的就是在BazMiddleware中,没有调用下一个中间件,因此404中间件便不会得到触发处理的机会。

下图是最后的执行结果:

3小结

经过蒋金楠老师的讲解以及自己的学习,对这个Mini版的ASP.NET Core框架有了一个初步的理解,正如蒋老师所说,ASP.NET Core的核心就在于由一个服务器和若干中间件构成的管道,了解了这一点,就对ASP.NET Core的核心本质有了大概印象。当然,这个Mini版的ASP.NET Core只是模拟了ASP.NET Core的冰山一角,还有许多的特性都没有,比如基于Starup来注册中间件,依赖注入框架,配置系统,预定义中间件等等等等,但是对于广大ASP.NET Core学习者来说是个绝佳的入门,是进阶学习ASP.NET Core源码的基石读物,最后感谢大内老A的分享!

最后让我们一起期待A大的《ASP.NET Core框架揭秘》早日出版!

4参考资料

蒋金楠,《200行代码,7个对象—让你了解ASP.NET Core框架的本质》

蒋金楠,《Inside ASP.NET Core Framework》

Edison Zhou,https://github.com/EdisonChou/AspNetCore.Mini

????点击阅读原文,观看A大分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/311378.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

根据后序和中序求二叉树的先序

代码如下&#xff1a; #include <iostream> using namespace std; const int N 1010; int in_order[N], post_order[N], lch[N], rch[N];int build(int inL, int inR, int postL, int postR) {if (inL > inR)return 0;int root post_order[postR];int k inL;while…

30多岁程序员老W,无奈选择转行!问题出在哪?

作者&#xff1a;邹溪源&#xff0c;长沙资深互联网从业者&#xff0c;架构师社区特邀嘉宾&#xff01;一有一天&#xff0c;一位同事跟我说&#xff1a;老w已经改行做美缝去了&#xff0c;你怎么看&#xff1f;我想了想&#xff0c;说&#xff1a;他大概终于做出了眼下最符合他…

一维前缀和

代码如下&#xff1a; #include <iostream> using namespace std; const int N 1010; int a[N], s[N];int main() {int n;cin >> n;for (int i 1; i < n; i) {cin >> a[i];//原数组s[i] s[i - 1] a[i];}for (int i 1; i < n; i)cout << s[…

HttpClientFactory的套路,你知多少?

背景ASP.NET Core 在 2.1 之后推出了具有弹性 HTTP 请求能力的 HttpClient 工厂类 HttpClientFactory。替换的初衷还是简单摆一下&#xff1a;① using(var client new HttpClient()) 调用的 Dispose() 方法并不会立即释放底层 Socket 连接&#xff0c;新建 Socket 需要时间&a…

走迷宫-双向bfs解法

双向bfs适用于知道起点和终点的状态下使用&#xff0c;从起点和终点两个方向开始进行搜索&#xff0c;可以非常大的提高单个bfs的搜索效率同样&#xff0c;实现也是通过队列的方式&#xff0c;可以设置两个队列&#xff0c;一个队列保存从起点开始搜索的状态&#xff0c;另一个…

.NET Core开发实战(第16课:选项数据热更新:让服务感知配置的变化)--学习笔记...

16 | 选项数据热更新&#xff1a;让服务感知配置的变化选项框架还有两个关键类型&#xff1a;1、IOptionsMonitor2、IOptionsSnapshot场景&#xff1a;1、范围作用域类型使用 IOptinsSnapshot2、单例服务使用 IOptionsMonitor通过代码更新选项&#xff1a;IPostConfigureOption…

华为任职资格_看了华为的任职资格体系,你就明白员工为啥这么拼?

提到华为的18万奋斗者&#xff0c;职场上没人不竖起大拇指。而优秀人才的背后&#xff0c;就像任正非曾说过的那样&#xff0c;华为的成功&#xff0c;很大意义上讲就是人力资源的成功。华为的人力资源管理最有代表性的&#xff0c;除了狼性文化和薪酬绩效外&#xff0c;就是任…

如何使用有序GUID提升数据库读写性能

源宝导读&#xff1a;数据库设计时&#xff0c;经常会使用GUID作为表的主键&#xff0c;但由于GUID的随机性会导致数据库在读写数据时效率严重下降&#xff0c;影响应用程序整体性能。本文将深入探讨如何通过使用有序GUID提升数据读写的性能。一、背景常见的数据库设计是使用连…

《商业洞察力30讲》学习笔记(上)

【洞察力】| 作者 / Edison Zhou这是恰童鞋骚年的第197篇原创文章学习洞察力&#xff0c;也是新时代IT人员的一门进阶必修课...1学习背景2019年下半年至今&#xff0c;在领导的推荐下学习了刘润老师的《商业洞察力30讲》&#xff0c;刷新了我对于事物的认知&#xff0c;也为我提…

[蓝桥杯][历届试题]九宫重排-双向bfs和map标记

题目描述 如下面第一个图的九宫格中&#xff0c;放着 1~8 的数字卡片&#xff0c;还有一个格子空着。与空格子相邻的格子中的卡片可以移动到空格中。经过若干次移动&#xff0c;可以形成第二个图所示的局面。 我们把第一个图的局面记为&#xff1a;12345678. 把第二个图的局面记…

3月数据库排行:前10整体下行,出新技术了?

DB-Engines 数据库流行度排行榜 3 月更新已发布&#xff0c;排名前二十如下&#xff1a;排名方面没有任何变动&#xff08;仅针对前十&#xff09;&#xff0c;相信很长一段时间内也都不会变动&#xff0c;毕竟巨头的位置不是一时半刻就能动摇的。不过这个月的排行榜还是有值得…

每日一题——LeetCode160.相交链表

个人主页&#xff1a;白日依山璟 专栏&#xff1a;Java|数据结构与算法|每日一题 文章目录 1. 题目描述示例1&#xff1a;示例2&#xff1a;提示&#xff1a; 2. 思路3. 代码 1. 题目描述 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的…

.NET Core的HttpClient连接池管理

译者荐语&#xff1a;使用.NET Core的HttpClient连接池管理有哪些注意事项&#xff1f;本文给出了非常中肯的建议。原文来自互联网&#xff0c;由长沙DotNET技术社区编译。如译文侵犯您的署名权或版权&#xff0c;请联系小编&#xff0c;小编将在24小时内删除。本文来源[1]史蒂…

[蓝桥杯][2014年第五届真题]兰顿蚂蚁-模拟

题目描述 兰顿蚂蚁&#xff0c;是于1986年&#xff0c;由克里斯兰顿提出来的&#xff0c;属于细胞自动机的一种。 平面上的正方形格子被填上黑色或白色。在其中一格正方形内有一只“蚂蚁”。 蚂蚁的头部朝向为&#xff1a;上下左右其中一方。 蚂蚁的移动规则十分简单&#…

.NET Core开发实战(第17课:为选项数据添加验证:避免错误配置的应用接收用户流量)--学习笔记...

17 | 为选项数据添加验证&#xff1a;避免错误配置的应用接收用户流量三种验证方法1、直接注册验证函数2、实现 IValidateOptions3、使用 Microsoft.Extensions.Options.DataAnnotations延用上一节代码需要添加验证的时候不能用 Configure&#xff0c;而用 AddOptions 方法//se…

分支程序与循环程序设计-汇编实验二

DATA SEGMENT ;定义数据段BUF DB -1, 20, 3, 30, -5, 15, 100, -54, 0, 4, 78, 99DB -12, 32, 3, 23, -7, 24, 60,-51 ;定义比较大小的数据DATA ENDS ;数据段结束ESEG SEGMENT ;定义附加段RES1 DB 0;定义结果存放区RES2 DB 0RES3 DB 0 ESEG ENDS ;附加段结束CODE SEGMENT ;定义…

[蓝桥杯][2013年第四届真题]剪格子-dfs

题目描述 历届试题 剪格子 时间限制&#xff1a;1.0s 内存限制&#xff1a;256.0MB 问题描述 如下图所示&#xff0c;3 x 3 的格子中填写了一些整数。 我们沿着图中的星号线剪开&#xff0c;得到两个部分&#xff0c;每个部分的数字和都是60。 本题的要求就是请你编程判定&a…

使用有序GUID:提升其在各数据库中作为主键时的性能

原文出处&#xff1a;https://www.codeproject.com/articles/388157/guids-as-fast-primary-keys-under-multiple-database &#xff0c;避免今后忘记了再去阅读原英文。【】是感觉理解有问题的地方正确的使用有序GUID在大部分数据库中可以获得和 整型作为主键 时相媲美的性能。…

串操作指令及其应用程序的设计与调试运行——汇编实验四 用8086汇编完成下题 编制一程序,从键盘输入两个长度不同的字符串,设字符串长度小于25个字符。要求在屏幕上以右边对齐的形式显示出

文章目录实验目的和内容实验要求和步骤实现右对齐以下为源码&#xff1a;方法一方法二方法三测试一——字符串的输入输出测试二——改进测试三——改进测试四——改进实验目的和内容 实验要求和步骤 实现右对齐 以下为源码&#xff1a; 方法一 DATA SEGMENTNUM EQU 25BUF1 D…

selenium.common.exceptions.WebDriverException: Message: ‘chromedriver‘ executable needs to bein PATH

使用Selenium模拟浏览器访问淘宝首页&#xff0c;出现报警 from selenium import webdriver import timebrowser webdriver.Chrome() browser.get(https://www.taobao.com) time.sleep(2) print(browser.page_source)selenium.common.exceptions.WebDriverException: Message…