AspNetCore管道
title: Asp.Net Core底层源码剖析(一)中间件/管道
categories: 后端
tags:
- .NET
当我们像下面这样添加一个管道时发生了什么?
app.Use(async (httpcontext, next) =>
{
Console.WriteLine("做一些业务处理");
// do sth here...
// 调用下一个管道
await next();
});
接着我们来看下Use的源码,首先需要知道的是,.Net Core 源码内部ApplicationBuilder
类维护了一个管道的委托调用链:
private readonly List<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();
public delegate Task RequestDelegate(HttpContext context);
// 上面的Use先调用了这个
public static IApplicationBuilder Use(
this IApplicationBuilder app,
Func<HttpContext, Func<Task>, Task> middleware)
{
// 这里的Use调用下面的Use
return app.Use((Func<RequestDelegate, RequestDelegate>) (next => (RequestDelegate) (context =>
{
Func<Task> func = (Func<Task>) (() => next(context));
return middleware(context, func);
})));
}
// 真正的Use
public IApplicationBuilder Use(
Func<RequestDelegate, RequestDelegate> middleware)
{
this._components.Add(middleware);
return (IApplicationBuilder) this;
}
从上面四段代码我们可以得到这样的信息:一个管道其实就是一个Func<RequestDelegate, RequestDelegate>
类型的委托,接收一个RequestDelegate
类型的委托,同时也返回一个RequestDelegate
类型的委托,这个RequestDelegate
委托的参数又是一个HttpContext,每次调用Use添加中间件时都会往_components
这个数组的最后添加这个新的管道,下面这段是核心代码:
app.Use((Func<RequestDelegate, RequestDelegate>) (next => (RequestDelegate) (context =>
{
Func<Task> func = (Func<Task>) (() => next(context));
return middleware(context, func);
})));
这个Func<RequestDelegate, RequestDelegate>
中第一个参数是入参,也就是调用下一个管道的委托,第二个参数是返回值,也就是调用当前管道的委托,这里比较难理解,我们需要分解来看:
context =>
{
Func<Task> func = (Func<Task>) (() => next(context));
return middleware(context, func);
}
这是一个RequestDelegate
委托,里面做的事情就是把调用下一个管道的操作封装成立一个Func
委托,然后传入当前的Func<HttpContext, Func<Task>, Task> middleware
委托中,也就是我们最开始写的那一段代码,调用下一个管道的委托对应了我们自己代码中的next
委托,在知道这部分的作用后,我们简化一下,记作:A
(Func<RequestDelegate, RequestDelegate>) (next => (RequestDelegate) (A))
简化之后替换一下,再看,就会发现这是一个Func<RequestDelegate, RequestDelegate>
委托,第一个参数是调用下一个管道的委托,第二个是返回值,代表了调用当前管道的委托(套娃操作,这一步最难理解,需要多看几遍),这样做有什么用呢?继续往下看
最后在构建WebHostBuilder的时候,内部Build
函数会利用这个来组装整个调用链:
/// <summary>
/// Produces a <see cref="T:Microsoft.AspNetCore.Http.RequestDelegate" /> that executes added middlewares.
/// </summary>
/// <returns>The <see cref="T:Microsoft.AspNetCore.Http.RequestDelegate" />.</returns>
public RequestDelegate Build()
{
RequestDelegate requestDelegate = (RequestDelegate) (context =>
{
Endpoint endpoint = context.GetEndpoint();
if (endpoint?.RequestDelegate != null)
throw new InvalidOperationException("The request reached the end of the pipeline without executing the endpoint: '" + endpoint.DisplayName + "'. Please register the EndpointMiddleware using 'IApplicationBuilder.UseEndpoints(...)' if using routing.");
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
for (int index = this._components.Count - 1; index >= 0; --index)
requestDelegate = this._components[index](requestDelegate);
return requestDelegate;
}
要注意的是这里是从后往前遍历的,这里解释一下上面这段代码的作用:
- 首先先使用GetEndPoint()函数获取管道的终结点,可以简单理解为找出http请求最终要调用到的方法
- 上一步中得到了管道的最后一个位置,然后开始从后往前遍历所有的管道,并且把调用下一个管道的委托传入上一个管道作为参数
理解了上面说的话之后其实一切都已经清晰了,在委托链组装完毕后,第一个RequestDelegate
内部的逻辑其实就是处理自己管道内部的业务,然后调用下一个requestDelegate
(也就是next),然后一直调用下去直到到达最后的终点,或者中间没有调用next(),再总结一下:
利用上面已经组装好的委托链,当一个http请求进来的时候,.Net Core内部就会把这个http请求转换成一个HttpContext类型的参数,然后从一个管道开始处理,当一个管道处理完成后,会调用下一个委托并传入这个HttpContext继续处理相应逻辑,当然,如果你自定义的管道没有调用触发下一个管道的RequestDelegate
委托,那么请求就到此中断了
我们可以发起一个请求debug调试看看是不是这样,在发起一个http请求后,我们会发现调用到了下面这段代码:
var context = application.CreateContext(this);
try
{
KestrelEventSource.Log.RequestStart(this);
// 重点!!
// Run the application code for this request
await application.ProcessRequestAsync(context);
// Trigger OnStarting if it hasn't been called yet and the app hasn't
// already failed. If an OnStarting callback throws we can go through
// our normal error handling in ProduceEnd.
// https://github.com/aspnet/KestrelHttpServer/issues/43
if (!HasResponseStarted && _applicationException == null && _onStarting?.Count > 0)
{
await FireOnStarting();
}
if (!_connectionAborted && !VerifyResponseContentLength(out var lengthException))
{
ReportApplicationError(lengthException);
}
}
ProcessRequestAsync
这个函数的调用如下:
private readonly RequestDelegate _application;
//...其他省略代码
public Task ProcessRequestAsync(HostingApplication.Context context) => this._application(context.HttpContext);
从这里我们就可以看出,_application
是第一个委托,调用到这个之后很自然的就跟我们上面说得一样了,一直往下调用就完事了
总的来说,其实管道调用链组装部分其实不难理解,最难理解的是Use
那段核心代码,委托套委托,每次看都会绕晕,得花很长时间才能理清里面的逻辑,如果看完之后还是觉得没看懂的话,可以自己建一个.NET 6版本的Minimal Api的Webapi项目打开源码慢慢分析,相信多花些时间肯定还是能看明白的
AspNetCore管道的更多相关文章
- 客官,来看看AspNetCore的身份验证吧
开篇 这段时间潜水了太久,终于有时间可以更新一篇文章了. 通过本篇文章您将Get: Http的一些身份验证概念 在AspNetCore中实现身份验证方案 JWT等概念的基础知识 使用Bearer To ...
- 【aspnetcore】模拟中间件处理请求的管道
几个核心对象: ApplicationBuilder 就是startup->Configure方法的第一个参数,请求(HttpContext) 就是由这个类来处理的 HttpContext 这个 ...
- ASP.NET Core HTTP 管道中的那些事儿
前言 马上2016年就要过去了,时间可是真快啊. 上次写完 Identity 系列之后,反响还不错,所以本来打算写一个 ASP.NET Core 中间件系列的,但是中间遇到了很多事情.首先是 NPOI ...
- 学习ASP.NET Core, 怎能不了解请求处理管道[6]: 管道是如何随着WebHost的开启被构建出来的?
注册的服务器和中间件共同构成了ASP.NET Core用于处理请求的管道, 这样一个管道是在我们启动作为应用宿主的WebHost时构建出来的.要深刻了解这个管道是如何被构建出来的,我们就必须对WebH ...
- NET Core HTTP 管道
ASP.NET Core HTTP 管道中的那些事儿 前言 马上2016年就要过去了,时间可是真快啊. 上次写完 Identity 系列之后,反响还不错,所以本来打算写一个 ASP.NET Cor ...
- ASP.NET Core 源码阅读笔记(5) ---Microsoft.AspNetCore.Routing路由
这篇随笔讲讲路由功能,主要内容在项目Microsoft.AspNetCore.Routing中,可以在GitHub上找到,Routing项目地址. 路由功能是大家都很熟悉的功能,使用起来也十分简单,从 ...
- ASP.NET Core 源码阅读笔记(3) ---Microsoft.AspNetCore.Hosting
有关Hosting的基础知识 Hosting是一个非常重要,但又很难翻译成中文的概念.翻译成:寄宿,大概能勉强地传达它的意思.我们知道,有一些病毒离开了活体之后就会死亡,我们把那些活体称为病毒的宿主. ...
- Microsoft.AspNetCore.Routing路由
Microsoft.AspNetCore.Routing路由 这篇随笔讲讲路由功能,主要内容在项目Microsoft.AspNetCore.Routing中,可以在GitHub上找到,Routing项 ...
- AspNetCore.Hosting
Microsoft.AspNetCore.Hosting 有关Hosting的基础知识 Hosting是一个非常重要,但又很难翻译成中文的概念.翻译成:寄宿,大概能勉强地传达它的意思.我们知道,有一些 ...
- ASP.NET Core 2.0 : 八.图说管道
本文通过一张GIF动图来继续聊一下ASP.NET Core的请求处理管道,从管道的配置.构建以及请求处理流程等方面做一下详细的研究.(ASP.NET Core系列目录) 一.概述 上文说到,请求是经过 ...
随机推荐
- 一篇文章带你了解网页框架——Vue简单入门
一篇文章带你了解网页框架--Vue简单入门 这篇文章将会介绍我们前端入门级别的框架--Vue的简单使用 如果你以后想从事后端程序员,又想要稍微了解前端框架知识,那么这篇文章或许可以给你带来帮助 温馨提 ...
- JavaScript中通过按回车键进行数据的录入
1.代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <ti ...
- virtualenv +virtualenvwrapper
一.虚拟环境virtualenv 1.安装:pip3 install virtualenv 2.创建虚拟环境:virtualenv venv #venv为虚拟环境目录名,目录名自定义 #virtual ...
- C# Static关键词的使用
一.C#中类的方法分为静态方法和非静态方法 静态方法有Static关键词修饰 静态方法归类所有,而非静态方法归类的实例所有:静态方法无需类去实例化可直接调用 静态成员属于类所有,为各个类的实例所公用, ...
- 小样本利器4. 正则化+数据增强 Mixup Family代码实现
前三章我们陆续介绍了半监督和对抗训练的方案来提高模型在样本外的泛化能力,这一章我们介绍一种嵌入模型的数据增强方案.之前没太重视这种方案,实在是方法过于朴实...不过在最近用的几个数据集上mixup的表 ...
- LoadRunner11脚本小技能之同步/异步接口分离+批量替换请求头
最近在公司又进行了一次LoadRunner11性能测试,技能又get了一点,继续Mark起来!!! 一.异步/同步接口分离 之前在另一篇博文中有提到"事务拆分"的小节,即一个htm ...
- Python的几种lambda排序方法
1.对单个变量进行排序 #lst = [[5,8],[5,3],[3,1]] lst.sort(key = lambda x : x[1]) #lst = [[3,1],[5,8],[5,3]] 以元 ...
- 2022-11-09 Acwing每日一题
本系列所有题目均为Acwing课的内容,发表博客既是为了学习总结,加深自己的印象,同时也是为了以后回过头来看时,不会感叹虚度光阴罢了,因此如果出现错误,欢迎大家能够指出错误,我会认真改正的.同时也希望 ...
- JavaScript代码是怎么在浏览器里面运行起来的?
JavaScript代码是怎么在浏览器里面运行的?下面简单探索一下 浏览器内核 浏览器内核(Rendering Engine),常见的叫法如:排版引擎.解释引擎.渲染引擎,现在流行称为浏览器内核. 浏 ...
- 【Java并发011】原理层面:CAS操作全解析
一.前言 volatile关键字是Java51个关键字中用的比较少的一个,它是一个与多线程并发的关键字,但是实际开发中,一般不会用到,使用synchronize+wait()+notify()/not ...