跟我一起学.NetCore之中间件(Middleware)简介和解析请求管道构建

前言

中间件(Middleware)对于Asp.NetCore项目来说,不能说重要,而是不能缺少,因为Asp.NetCore的请求管道就是通过一系列的中间件组成的;在服务器接收到请求之后,请求会经过请求管道进行相关的过滤或处理;

正文

那中间件是那路大神?

会经常听说,需要注册一下中间件,如图:

所以说,中间件是针对请求进行某种功能需求封装的组件,而这个组件可以控制是否继续执行下一个中间件;如上图中的app.UserStaticFiles()就是注册静态文件处理的中间件,在请求管道中就会处理对应的请求,如果没有静态文件中间件,那就处理不了静态文件(如html、css等);这也是Asp.NetCore与Asp.Net不一样的地方,前者是根据需求添加对应的中间件,而后者是提前就全部准备好了,不管用不用,反正都要路过,这也是Asp.NetCore性能比较好的原因之一;

而对于中间件执行逻辑,官方有一个经典的图:

如图所示,请求管道由一个个中间件(Middleware)组成,每个中间件可以在请求和响应中进行相关的逻辑处理,在有需要的情况下,当前的中间件可以不传递到下一个中间件,从而实现断路;如果这个不太好理解,如下图:

每层外圈代表一个中间件,黑圈代表最终的Action方法,当请求过来时,会依次经过中间件,Action处理完成后,返回响应时也依次经过对应的中间件,而执行的顺序如箭头所示;(这里省去了一些其他逻辑,只说中间件)。

好了好了,理论说不好,担心把看到的小伙伴绕进去了,就先到这吧,接下来从代码中看看中间件及请求管道是如何实现的;老规矩,找不到下手的地方,就先找能"摸"的到的地方,这里就先扒静态文件的中间件:

namespace Microsoft.AspNetCore.Builder
{public static class StaticFileExtensions{// 调用就是这个扩展方法public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app){if (app == null){throw new ArgumentNullException(nameof(app));}// 这里调用了 IApplicationBuilder 的扩展方法return app.UseMiddleware<StaticFileMiddleware>();}// 这里省略了两个重载方法,是可以指定参数的}
}

UseMiddleware方法实现

// 看着调用的方法
public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object[] args)
{// 内部调用了以下方法return app.UseMiddleware(typeof(TMiddleware), args);
}
// 其实这里是对自定义中间件的注册,这里可以不用太深入了解
public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
{if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo())){// IMiddleware doesn't support passing args directly since it's// activated from the containerif (args.Length > 0){throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));}return UseMiddlewareInterface(app, middleware);}// 取得容器var applicationServices = app.ApplicationServices;// 反编译进行包装成注册中间件的样子(Func<ReuqestDelegate,RequestDelegate>),但可以看到本质使用IApplicationBuilder中Use方法return app.Use(next =>{// 获取指定类型中的方法列表var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);// 找出名字是Invoke或是InvokeAsync的方法var invokeMethods = methods.Where(m =>string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)|| string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)).ToArray();// 如果有多个方法 ,就抛出异常,这里保证方法的唯一if (invokeMethods.Length > 1){throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));}// 如果没有找到,也就抛出异常if (invokeMethods.Length == 0){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));}// 取得唯一的方法Invoke或是InvokeAsync方法var methodInfo = invokeMethods[0];// 判断类型是否返回Task,如果不是就抛出异常,要求返回Task的目的是为了后续包装RequestDelegateif (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType)){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));}// 判断方法的参数,参数的第一个参数必须是HttpContext类型var parameters = methodInfo.GetParameters();if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext)){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));}// 开始构造RequestDelegate对象var ctorArgs = new object[args.Length + 1];ctorArgs[0] = next;Array.Copy(args, 0, ctorArgs, 1, args.Length);var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);// 如果参数只有一个HttpContext 就包装成一个RequestDelegate返回if (parameters.Length == 1){return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance);}// 如果参数有多个的情况就单独处理,这里不详细进去了var factory = Compile<object>(methodInfo, parameters);return context =>{var serviceProvider = context.RequestServices ?? applicationServices;if (serviceProvider == null){throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));}return factory(instance, context, serviceProvider);};});
}

以上代码其实现在拿出来有点早了,以上是对自定义中间件的注册方式,为了扒代码的逻辑完整,拿出来了;这里可以不用深究里面内容,知道内部调用了IApplicationBuilder的Use方法即可;

由此可见,IApplicationBuilder就是构造请求管道的核心类型,如下:

namespace Microsoft.AspNetCore.Builder
{public interface IApplicationBuilder{// 容器,用于依赖注入获取对象的IServiceProvider ApplicationServices{get;set;}// 属性集合,用于中间件共享数据IDictionary<string, object> Properties{get;}// 针对服务器的特性IFeatureCollection ServerFeatures{get;}// 构建请求管道RequestDelegate Build();// 克隆实例的IApplicationBuilder New();// 注册中间件IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);}
}

IApplicationBuilder的默认实现就是ApplicationBuilder,走起,一探究竟:

namespace Microsoft.AspNetCore.Builder
{   // 以下 删除一些属性和方法,具体可以私下看具体代码public class ApplicationBuilder : IApplicationBuilder{// 存储注册中间件的链表private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();// 注册中间件public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware){// 将中间件加入到链表_components.Add(middleware);return this;}// 构造请求管道public RequestDelegate Build(){// 构造一个404的中间件,这就是为什么地址匹配不上时会报404的原因RequestDelegate app = context =>{// 判断是否有Endpoint中间件var endpoint = context.GetEndpoint();var endpointRequestDelegate = endpoint?.RequestDelegate;if (endpointRequestDelegate != null){var message =$"The request reached the end of the pipeline without executing the endpoint: '{endpoint.DisplayName}'. " +$"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +$"routing.";throw new InvalidOperationException(message);}// 返回404 Codecontext.Response.StatusCode = 404;return Task.CompletedTask;};// 构建管道,首先将注册的链表倒序一把,保证按照注册顺序执行foreach (var component in _components.Reverse()){app = component(app);}// 最终返回return app;}}
}

在注册的代码中,可以看到所谓的中间件就是Func<RequestDelegate, RequestDelegate>,其中RequestDelegate就是一个委托,用于处理请求的,如下:

public delegate Task RequestDelegate(HttpContext context);

之所以用Func<RequestDelegate, RequestDelegate>的形式表示中间件,应该就是为了中间件间驱动方便,毕竟中间件不是单独存在的,是需要多个中间件结合使用的;

那请求管道构造完成了,那请求是如何到管道中呢?

应该都知道,Asp.NetCore内置了IServer,负责监听对应的请求,当请求过来时,会将请求给IHttpApplication<TContext>进行处理,简单看一下接口定义:

namespace Microsoft.AspNetCore.Hosting.Server
{public interface IHttpApplication<TContext>
{// 执行上下文创建TContext CreateContext(IFeatureCollection contextFeatures);// 执行上下文释放void DisposeContext(TContext context, Exception exception);// 处理请求,这里就使用了请求管道处理Task ProcessRequestAsync(TContext context);}
}

而对于IHttpApplication<TContext>类型来说,默认创建的就是HostingApplication,如下:

namespace Microsoft.AspNetCore.Hosting
{internal class HostingApplication : IHttpApplication<HostingApplication.Context>{// 构建出来的请求管道private readonly RequestDelegate _application;// 用于创建请求上下文的private readonly IHttpContextFactory _httpContextFactory;private readonly DefaultHttpContextFactory _defaultHttpContextFactory;private HostingApplicationDiagnostics _diagnostics;// 构造函数初始化变量public HostingApplication(RequestDelegate application,ILogger logger,DiagnosticListener diagnosticSource,IHttpContextFactory httpContextFactory){_application = application;_diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource);if (httpContextFactory is DefaultHttpContextFactory factory){_defaultHttpContextFactory = factory;}else{_httpContextFactory = httpContextFactory;}}// 创建对应的请求的上下文public Context CreateContext(IFeatureCollection contextFeatures){Context hostContext;if (contextFeatures is IHostContextContainer<Context> container){hostContext = container.HostContext;if (hostContext is null){hostContext = new Context();container.HostContext = hostContext;}}else{// Server doesn't support pooling, so create a new ContexthostContext = new Context();}HttpContext httpContext;if (_defaultHttpContextFactory != null){var defaultHttpContext = (DefaultHttpContext)hostContext.HttpContext;if (defaultHttpContext is null){httpContext = _defaultHttpContextFactory.Create(contextFeatures);hostContext.HttpContext = httpContext;}else{_defaultHttpContextFactory.Initialize(defaultHttpContext, contextFeatures);httpContext = defaultHttpContext;}}else{httpContext = _httpContextFactory.Create(contextFeatures);hostContext.HttpContext = httpContext;}_diagnostics.BeginRequest(httpContext, hostContext);return hostContext;}// 将创建出来的请求上下文交给请求管道处理public Task ProcessRequestAsync(Context context){// 请求管道处理return _application(context.HttpContext);}// 以下删除了一些代码,具体可下面查看....}
}

这里关于Server监听到请求及将请求交给中间处理的具体过程没有具体描述,可以结合启动流程和以上内容在细扒一下流程吧(大家私下搞吧),这里就简单说说中间件及请求管道构建的过程;(后续有时间将整体流程走一遍);

总结

这节又是纯代码来“忽悠”小伙伴了,对于理论概念可能表达的不够清楚,欢迎交流沟通;其实这里只是根据流程走了一遍源码,并没有一行行解读,所以小伙伴看此篇文章代码部分的时候,以调试的思路去看,从注册中间件那块开始,到最后请求交给请求管道处理,注重这个流程即可;

下一节说说中间件的具体应用;

------------------------------------------------

一个被程序搞丑的帅小伙,关注"Code综艺圈",识别关注跟我一起学~~~

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

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

相关文章

leetcode647. 回文子串

一&#xff1a;题目 二&#xff1a;上码 class Solution { public:/**思路:动态规划五步走1>:确定dp数组以及下标的含义dp[i][j] 表示的是在[i,j]范围内的字串 是否是 回文子串&#xff0c;如果是的话那么dp[i][j] true2>确定dp数组的状态转移方程那么就有两种情况 s[i…

leetcode516. 最长回文子序列

一:题目 二:上码 class Solution { public:/**思路:1.分析题意 这个是让我们求最值,那么首先想到动态规划2.动态规划1>:确定dp数组以及下标的含义dp[i][j] 表示字符串在[i,j]范围内的最长回文子序列2>:确定dp数组的状态递推公式那么就是s[i] 与 s[j] 相等 不相等两种情况…

C#刷剑指Offer | 二叉搜索树的后序遍历序列

【C#刷题】| 作者 / Edison Zhou这是EdisonTalk的第289篇原创内容我们来用之前学到的数据结构知识来刷《剑指Offer》的一些核心题目&#xff08;精选了其中30道题目&#xff09;&#xff0c;希望对你有帮助&#xff01;本文题目为&#xff1a;二叉搜索树的后序遍历序列。1题目介…

leetcode739. 每日温度

一:题目 二:上码 // class Solution { // public: // vector<int> dailyTemperatures(vector<int>& temperatures) { // vector<int> ans(temperatures.size(),0);// for (int i 0; i < temperatures.size(); i) {// …

Leetcode周赛复盘——第 71 场力扣双周赛与第 279 场力扣周赛

双周赛&#xff1a; 5984. 拆分数位后四位数字的最小和 class Solution:def minimumSum(self, num: int) -> int:a, b, c, d sorted(list(map(int, str(num))))return 10 * (a b) c dstr(num)得到字符串序列&#xff0c;然后用map函数对序列的每个字符转换为数字&…

使用SWAGGER和ASP.NET CORE设置可选路由参数

使用SWAGGER和ASP.NET CORE设置可选路由参数根据OpenAPI 3.0&#xff0c;这是不可能的。但是&#xff0c;如果您真的希望成为现实呢&#xff1f;您是否必须解决并允许您的Swagger文档出错&#xff1f;我在这里向您展示如何使用Swagger和ASP.NET Core设置可选的路由参数。等等&a…

在数组中找重复数、只出现一次的数或丢失数的题目(Leetcode题解-Python语言)

在一维数组中的考察中&#xff0c;最常见的就是找出数组中的重复数、只出现一次的数或者丢失&#xff08;消失&#xff09;数等等。 一般来说&#xff0c;首先想到的就是用哈希表&#xff08;集合&#xff09;来记录出现过的数&#xff0c;基本所有的题都可以用集合来做&#…

Confluent官博:Kafka最牛队列,性能15倍于RabbitMQ!

“容器、Kubernetes、DevOps、微服务、云原生&#xff0c;这些技术名词的频繁出现&#xff0c;预兆着新的互联网技术时代的到来&#xff0c;大数据高并发将不再遥远&#xff0c;而是大部分项目都必须面对的&#xff0c;消息队列则是核心利器&#xff01;成熟的消息队列产品很多…

leetcode503. 下一个更大元素 II

一:题目 二:上码 class Solution { public:/**思路: 1.将两个nums拼接到一块这里拼接到一块,当我们最后的元素找不到比其大的时候 就会开始从头开始这样的话就可以继续进行 入栈 或者出栈的操作入栈就是比我栈顶小的元素&#xff0c;出栈的话 那就是 找到了比其大的元素了…

跟我一起学.NetCore之中间件(Middleware)应用和自定义

前言Asp.NetCore中的请求管道是通过一系列的中间件组成的&#xff0c;使得请求会根据需求进行对应的过滤和加工处理。在平时开发中会时常引用别人定义好的中间件&#xff0c;只需简单进行app.Usexxx就能完成中间件的注册&#xff0c;但是对于一些定制化需求还得自己进行处理和封…

leetcode42. 接雨水

一:题目 二:上码 // class Solution { // public: // /**超时 // 思路: // 1.我们按列来计算 这就是表明的是 我们求取接雨水 向上的高度就是雨水量 // 但是这里的话我们的需要对雨水的高度 来进行判定 // 2.那么如何判定…

Magicodes.IE之导入导出筛选器

总体设计Magicodes.IE是一个导入导出通用库&#xff0c;支持Dto导入导出以及动态导出&#xff0c;支持Excel、Word、Pdf、Csv和Html。在本篇教程&#xff0c;笔者将讲述如何使用Magicodes.IE的导入导出筛选器。在开始之前&#xff0c;我们需要先了解Magicodes.IE目前支持的筛选…

谈了千百遍的缓存数据的一致性问题

“灵魂拷问保证缓存和数据库的一致性很简单吗&#xff1f;有哪些方式能保证缓存和数据库的一致性呢&#xff1f;如果发生了缓存和数据库数据不一致的情况怎么办呢&#xff1f;在上篇文章我们介绍了缓存的定义分类以及优缺点等&#xff0c;如果还没看的同学可以移步这里听说你会…

BS作业 基于springboot + Thymeleaf +mybatis 实现的书城管理系统

一:项目背景 项目描述 一个基本功能较为完整的后台管理项目。项目主要功能有&#xff1a;登录验证&#xff0c;登录功能还加入了随机验证码的验证&#xff1b; 用户注册&#xff0c;注册中密码基于srping 安全框架提供的加密(自动加盐)的密码储存方式&#xff0c;对注册重名进…

Istio Pilot 源码分析(二)

张海东&#xff0c; ‍多点生活&#xff08;成都&#xff09;云原生开发工程师。本篇主要介绍 Pilot 源码中的 ServiceEntryStore 及其推送 xDS 的流程。本文为 Istio Pilot 源码分析系列的第二篇文章。Istio Pilot 源码分析&#xff08;一&#xff09;了解了 Pilot 源码的基本…

Pytorch中的 torch.Tensor() 和 torch.tensor() 的区别

直接在搜索引擎里进行搜索&#xff0c;可以看到官方文档中两者对应的页面&#xff1a; 分别点击进去&#xff0c;第一个链接解释了什么是 torch.Tensor&#xff1a; torch.Tensor 是一个包含单一数据类型元素的多维矩阵&#xff08;数组&#xff09;。 正因为 torch.Tensor 只包…

leetcote34. 在排序数组中查找元素的第一个和最后一个位置

一:题目 二&#xff1a;上码&#xff08;暴力二分&#xff09; // class Solution { // public: // /** // 思路:1.首先这是一个升序的 那么相同的一定是会相连的// */// vector<int> searchRange(vector<int>& nums, int target) {// …

Git 图形化操作之合并提交记录

Git 图形化操作之合并提交记录独立观察员 2020 年 9 月 24 日目录1、显示日志2、合并提交记录3、推送合并的提交前言&#xff1a;当我们使用 Git 时&#xff0c;有时会遇到刚提交推送完一次修改&#xff0c;发现漏了该某处&#xff0c;只好又提交推送一次&#xff0c;这样在提交…

Pytorch中的 torch.as_tensor() 和 torch.from_numpy() 的区别

之前我写过一篇文章&#xff0c;比较了 torch.Tensor() 和 torch.tensor() 的区别&#xff0c;而这两者都是深拷贝的方法&#xff0c;返回张量的同时&#xff0c;会在内存中创建一个额外的数据副本&#xff0c;与原数据不共享内存&#xff0c;所以不受原数据改变的影响。 这里…