跟我一起学.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 container
if (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的目的是为了后续包装RequestDelegate
if (!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 Code
context.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(如Kestrel),负责监听对应的请求,当请求过来时,会将请求给IHttpApplication进行处理,简单看一下接口定义:
namespace Microsoft.AspNetCore.Hosting.Server
{
public interface IHttpApplication<TContext>
{
// 执行上下文创建
TContext CreateContext(IFeatureCollection contextFeatures);
// 执行上下文释放
void DisposeContext(TContext context, Exception exception);
// 处理请求,这里就使用了请求管道处理
Task ProcessRequestAsync(TContext context);
}
}
而对于IHttpApplication类型来说,默认创建的就是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 Context
hostContext = 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综艺圈",识别关注跟我一起学~~~
跟我一起学.NetCore之中间件(Middleware)简介和解析请求管道构建的更多相关文章
- 跟我一起学.NetCore之中间件(Middleware)应用和自定义
前言 Asp.NetCore中的请求管道是通过一系列的中间件组成的,使得请求会根据需求进行对应的过滤和加工处理.在平时开发中会时常引用别人定义好的中间件,只需简单进行app.Usexxx就能完成中间件 ...
- 跟我一起学.NetCore之静态文件处理的那些事
前言 如今前后端分离开发模式如火如荼,开发职责更加分明(当然前后端一起搞的模式也没有完全褪去):而对于每个公司产品实施来说,部署模式会稍有差别,有的会单独将前端文件部署为一个站点,有的会将前端文件和后 ...
- Asp.NetCore Web应用程序中的请求管道和中间件
你是否会迷惑当我们请求一个ASP.NetWeb应用程序以后,它是怎么处理这些请求的,后台是怎么工作的,今天就讲一下Asp.NetCore Web应用程序中的请求处理过程. 上一节,我们讲到,Start ...
- .NET Core 3.0 中间件 Middleware
中间件官网文档解释:中间件是一种装配到应用管道以处理请求和响应的软件 每个中间件: 选择是否将请求传递到管道中的下一个组件. 可在管道中的下一个组件前后执行工作. 使用 IApplicationBui ...
- Core篇——初探Core的Http请求管道&&Middleware
目录: 1.Core 处理HTTP请求流程 2.中间件(Middleware)&&处理流程 3.创建自定义中间件&&模拟Core的请求管道 Core 处理HTTP请求流 ...
- 跟我一起学.NetCore之WebApi接口裸奔有风险(Jwt)
前言 撸码需谨慎,裸奔有风险.经常在一些技术交流群中了解到,还有很多小伙伴的项目中Api接口没有做任何安全机制验证,直接就裸奔了,对于一些临时项目或是个人小项目还好,其余的话,建议小伙伴们酌情考虑都加 ...
- 跟我一起学.NetCore之MVC过滤器,这篇看完走路可以仰着头走
前言 MVC过滤器在之前Asp.Net的时候就已经广泛使用啦,不管是面试还是工作,总有一个考点或是需求涉及到,可以毫不疑问的说,这个技术点是非常重要的: 在之前参与的面试中,得知很多小伙伴只知道有一两 ...
- Django中间件(Middleware)处理请求
关注公众号"轻松学编程"了解更多. 1.面向切面编程 切点(钩子) 切点允许我们动态的在原有逻辑中插入一部分代码 在不修改原有代码的情况下,动态注入一部分代码 默认情况,不中断传播 ...
- ASP.NET Core 开发-中间件(Middleware)
ASP.NET Core开发,开发并使用中间件(Middleware). 中间件是被组装成一个应用程序管道来处理请求和响应的软件组件. 每个组件选择是否传递给管道中的下一个组件的请求,并能之前和下一组 ...
随机推荐
- [NewLife.Net]单机400万长连接压力测试
目标 对网络库NewLife.Net进行单机百万级长连接测试,并持续收发数据,检测网络库稳定性. [2020年8月1日晚上22点] 先上源码:https://github.com/NewLifeX/N ...
- 为什么需要将网站封装为app?
网站封装为app是一种宝贵的资源,为客户提供稳定的平台,一个网站也是一个有效的工具,用于企业与其客户之间的通信.企业网站用户可以通过他们的笔记本电脑,台式机,平板电脑,智能手机以及带有浏览器设备的 ...
- Eclipse RCP:多平台部署
1 问题 在使用Eclipse RCP IDE进行开发时,它自带的PDE(插件开发环境)工具仅能够导出相同平台的部署包,比如win32的仅能导出win32的,linux64仅能够导出linux64的. ...
- JAVAWEB开发下常见中文乱码问题解决
JAVA环境下处理中文乱码问题一直是很多人困扰的问题,像URL传参乱码,写进数据库乱码,服务写中文文字图片乱码处理及导出PDF乱码. 1:安装中文支持 yum groupinstall "f ...
- Chrome-AdGuard 无与伦比的广告拦截扩展
一款无与伦比的广告拦截扩展,对抗各式广告与弹窗. AdGuard 广告拦截器可有效的拦截所有网页上的所有类型的广告,甚至是在 Facebook.Youtube 以及其他万千网站上的广告! AdGuar ...
- 如何实现数据库CDP,即数据库连续数据保护
备份可以分为定期备份和实时备份.定期备份与实时备份相比存在两大劣势:一是备份需要时间窗口,对于很多24小时业务运行的机构,线上业务不允许有过多的业务系统停机去进行数据备份:二是定期备份无法保证数据丢失 ...
- 初始JAVA第十五章String的总结
字符串的概述 1.什么是字符串:零个或多个字符组成的有限序列 2.如何使用字符串:(使用字符串分为两步) 1)定义并初始化字符串 2)使用字符,对字符串进行一些处理 // 穿件一个字符串 // 语法 ...
- 【CF1174D】 Ehab and the Expected XOR Problem - 构造
题面 Given two integers \(n\) and \(x\), construct an array that satisfies the following conditions: · ...
- SpringSecurity权限管理系统实战—二、日志、接口文档等实现
系列目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战 ...
- unity探索者之UGUI图片描边
版权声明:本文为原创文章,转载请声明https://www.cnblogs.com/unityExplorer/p/13524270.html 自从UGUI出现之后,我就已经放弃使用NGUI了,原因不 ...