前言

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

正文

那中间件是那路大神?

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

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

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

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

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

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

  1. namespace Microsoft.AspNetCore.Builder
  2. {
  3. public static class StaticFileExtensions
  4. {
  5. // 调用就是这个扩展方法
  6. public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app)
  7. {
  8. if (app == null)
  9. {
  10. throw new ArgumentNullException(nameof(app));
  11. }
  12. // 这里调用了 IApplicationBuilder 的扩展方法
  13. return app.UseMiddleware<StaticFileMiddleware>();
  14. }
  15. // 这里省略了两个重载方法,是可以指定参数的
  16. }
  17. }

UseMiddleware方法实现

  1. // 看着调用的方法
  2. public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object[] args)
  3. {
  4. // 内部调用了以下方法
  5. return app.UseMiddleware(typeof(TMiddleware), args);
  6. }
  7. // 其实这里是对自定义中间件的注册,这里可以不用太深入了解
  8. public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
  9. {
  10. if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo()))
  11. {
  12. // IMiddleware doesn't support passing args directly since it's
  13. // activated from the container
  14. if (args.Length > 0)
  15. {
  16. throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
  17. }
  18. return UseMiddlewareInterface(app, middleware);
  19. }
  20. // 取得容器
  21. var applicationServices = app.ApplicationServices;
  22. // 反编译进行包装成注册中间件的样子(Func<ReuqestDelegate,RequestDelegate>),但可以看到本质使用IApplicationBuilder中Use方法
  23. return app.Use(next =>
  24. {
  25. // 获取指定类型中的方法列表
  26. var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
  27. // 找出名字是Invoke或是InvokeAsync的方法
  28. var invokeMethods = methods.Where(m =>
  29. string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
  30. || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
  31. ).ToArray();
  32. // 如果有多个方法 ,就抛出异常,这里保证方法的唯一
  33. if (invokeMethods.Length > 1)
  34. {
  35. throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
  36. }
  37. // 如果没有找到,也就抛出异常
  38. if (invokeMethods.Length == 0)
  39. {
  40. throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
  41. }
  42. // 取得唯一的方法Invoke或是InvokeAsync方法
  43. var methodInfo = invokeMethods[0];
  44. // 判断类型是否返回Task,如果不是就抛出异常,要求返回Task的目的是为了后续包装RequestDelegate
  45. if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
  46. {
  47. throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
  48. }
  49. // 判断方法的参数,参数的第一个参数必须是HttpContext类型
  50. var parameters = methodInfo.GetParameters();
  51. if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
  52. {
  53. throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
  54. }
  55. // 开始构造RequestDelegate对象
  56. var ctorArgs = new object[args.Length + 1];
  57. ctorArgs[0] = next;
  58. Array.Copy(args, 0, ctorArgs, 1, args.Length);
  59. var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
  60. // 如果参数只有一个HttpContext 就包装成一个RequestDelegate返回
  61. if (parameters.Length == 1)
  62. {
  63. return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance);
  64. }
  65. // 如果参数有多个的情况就单独处理,这里不详细进去了
  66. var factory = Compile<object>(methodInfo, parameters);
  67. return context =>
  68. {
  69. var serviceProvider = context.RequestServices ?? applicationServices;
  70. if (serviceProvider == null)
  71. {
  72. throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
  73. }
  74. return factory(instance, context, serviceProvider);
  75. };
  76. });
  77. }

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

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

  1. namespace Microsoft.AspNetCore.Builder
  2. {
  3. public interface IApplicationBuilder
  4. {
  5. // 容器,用于依赖注入获取对象的
  6. IServiceProvider ApplicationServices
  7. {
  8. get;
  9. set;
  10. }
  11. // 属性集合,用于中间件共享数据
  12. IDictionary<string, object> Properties
  13. {
  14. get;
  15. }
  16. // 针对服务器的特性
  17. IFeatureCollection ServerFeatures
  18. {
  19. get;
  20. }
  21. // 构建请求管道
  22. RequestDelegate Build();
  23. // 克隆实例的
  24. IApplicationBuilder New();
  25. // 注册中间件
  26. IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
  27. }
  28. }

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

  1. namespace Microsoft.AspNetCore.Builder
  2. { // 以下 删除一些属性和方法,具体可以私下看具体代码
  3. public class ApplicationBuilder : IApplicationBuilder
  4. {
  5. // 存储注册中间件的链表
  6. private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();
  7. // 注册中间件
  8. public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
  9. {
  10. // 将中间件加入到链表
  11. _components.Add(middleware);
  12. return this;
  13. }
  14. // 构造请求管道
  15. public RequestDelegate Build()
  16. {
  17. // 构造一个404的中间件,这就是为什么地址匹配不上时会报404的原因
  18. RequestDelegate app = context =>
  19. {
  20. // 判断是否有Endpoint中间件
  21. var endpoint = context.GetEndpoint();
  22. var endpointRequestDelegate = endpoint?.RequestDelegate;
  23. if (endpointRequestDelegate != null)
  24. {
  25. var message =
  26. $"The request reached the end of the pipeline without executing the endpoint: '{endpoint.DisplayName}'. " +
  27. $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +
  28. $"routing.";
  29. throw new InvalidOperationException(message);
  30. }
  31. // 返回404 Code
  32. context.Response.StatusCode = 404;
  33. return Task.CompletedTask;
  34. };
  35. // 构建管道,首先将注册的链表倒序一把,保证按照注册顺序执行
  36. foreach (var component in _components.Reverse())
  37. {
  38. app = component(app);
  39. }
  40. // 最终返回
  41. return app;
  42. }
  43. }
  44. }

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

  1. public delegate Task RequestDelegate(HttpContext context);

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

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

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

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

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

  1. namespace Microsoft.AspNetCore.Hosting
  2. {
  3. internal class HostingApplication : IHttpApplication<HostingApplication.Context>
  4. {
  5. // 构建出来的请求管道
  6. private readonly RequestDelegate _application;
  7. // 用于创建请求上下文的
  8. private readonly IHttpContextFactory _httpContextFactory;
  9. private readonly DefaultHttpContextFactory _defaultHttpContextFactory;
  10. private HostingApplicationDiagnostics _diagnostics;
  11. // 构造函数初始化变量
  12. public HostingApplication(
  13. RequestDelegate application,
  14. ILogger logger,
  15. DiagnosticListener diagnosticSource,
  16. IHttpContextFactory httpContextFactory)
  17. {
  18. _application = application;
  19. _diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource);
  20. if (httpContextFactory is DefaultHttpContextFactory factory)
  21. {
  22. _defaultHttpContextFactory = factory;
  23. }
  24. else
  25. {
  26. _httpContextFactory = httpContextFactory;
  27. }
  28. }
  29. // 创建对应的请求的上下文
  30. public Context CreateContext(IFeatureCollection contextFeatures)
  31. {
  32. Context hostContext;
  33. if (contextFeatures is IHostContextContainer<Context> container)
  34. {
  35. hostContext = container.HostContext;
  36. if (hostContext is null)
  37. {
  38. hostContext = new Context();
  39. container.HostContext = hostContext;
  40. }
  41. }
  42. else
  43. {
  44. // Server doesn't support pooling, so create a new Context
  45. hostContext = new Context();
  46. }
  47. HttpContext httpContext;
  48. if (_defaultHttpContextFactory != null)
  49. {
  50. var defaultHttpContext = (DefaultHttpContext)hostContext.HttpContext;
  51. if (defaultHttpContext is null)
  52. {
  53. httpContext = _defaultHttpContextFactory.Create(contextFeatures);
  54. hostContext.HttpContext = httpContext;
  55. }
  56. else
  57. {
  58. _defaultHttpContextFactory.Initialize(defaultHttpContext, contextFeatures);
  59. httpContext = defaultHttpContext;
  60. }
  61. }
  62. else
  63. {
  64. httpContext = _httpContextFactory.Create(contextFeatures);
  65. hostContext.HttpContext = httpContext;
  66. }
  67. _diagnostics.BeginRequest(httpContext, hostContext);
  68. return hostContext;
  69. }
  70. // 将创建出来的请求上下文交给请求管道处理
  71. public Task ProcessRequestAsync(Context context)
  72. {
  73. // 请求管道处理
  74. return _application(context.HttpContext);
  75. }
  76. // 以下删除了一些代码,具体可下面查看....
  77. }
  78. }

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

总结

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

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

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

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

跟我一起学.NetCore之中间件(Middleware)简介和解析请求管道构建的更多相关文章

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

    前言 Asp.NetCore中的请求管道是通过一系列的中间件组成的,使得请求会根据需求进行对应的过滤和加工处理.在平时开发中会时常引用别人定义好的中间件,只需简单进行app.Usexxx就能完成中间件 ...

  2. 跟我一起学.NetCore之静态文件处理的那些事

    前言 如今前后端分离开发模式如火如荼,开发职责更加分明(当然前后端一起搞的模式也没有完全褪去):而对于每个公司产品实施来说,部署模式会稍有差别,有的会单独将前端文件部署为一个站点,有的会将前端文件和后 ...

  3. Asp.NetCore Web应用程序中的请求管道和中间件

    你是否会迷惑当我们请求一个ASP.NetWeb应用程序以后,它是怎么处理这些请求的,后台是怎么工作的,今天就讲一下Asp.NetCore Web应用程序中的请求处理过程. 上一节,我们讲到,Start ...

  4. .NET Core 3.0 中间件 Middleware

    中间件官网文档解释:中间件是一种装配到应用管道以处理请求和响应的软件 每个中间件: 选择是否将请求传递到管道中的下一个组件. 可在管道中的下一个组件前后执行工作. 使用 IApplicationBui ...

  5. Core篇——初探Core的Http请求管道&&Middleware

    目录: 1.Core 处理HTTP请求流程 2.中间件(Middleware)&&处理流程 3.创建自定义中间件&&模拟Core的请求管道 Core 处理HTTP请求流 ...

  6. 跟我一起学.NetCore之WebApi接口裸奔有风险(Jwt)

    前言 撸码需谨慎,裸奔有风险.经常在一些技术交流群中了解到,还有很多小伙伴的项目中Api接口没有做任何安全机制验证,直接就裸奔了,对于一些临时项目或是个人小项目还好,其余的话,建议小伙伴们酌情考虑都加 ...

  7. 跟我一起学.NetCore之MVC过滤器,这篇看完走路可以仰着头走

    前言 MVC过滤器在之前Asp.Net的时候就已经广泛使用啦,不管是面试还是工作,总有一个考点或是需求涉及到,可以毫不疑问的说,这个技术点是非常重要的: 在之前参与的面试中,得知很多小伙伴只知道有一两 ...

  8. Django中间件(Middleware)处理请求

    关注公众号"轻松学编程"了解更多. 1.面向切面编程 切点(钩子) 切点允许我们动态的在原有逻辑中插入一部分代码 在不修改原有代码的情况下,动态注入一部分代码 默认情况,不中断传播 ...

  9. ASP.NET Core 开发-中间件(Middleware)

    ASP.NET Core开发,开发并使用中间件(Middleware). 中间件是被组装成一个应用程序管道来处理请求和响应的软件组件. 每个组件选择是否传递给管道中的下一个组件的请求,并能之前和下一组 ...

随机推荐

  1. Electron~增量更新

    增量更新说明文档 English Version 提前准备 准备本地或者远程服务器或者远程静态文件url npm i -g http-server cd yourFileFolder // 进入任意文 ...

  2. 4.深入k8s:容器持久化存储

    从一个例子入手PV.PVC Kubernetes 项目引入了一组叫作 Persistent Volume Claim(PVC)和 Persistent Volume(PV)的 API 对象用于管理存储 ...

  3. 虚拟机安装Centos(VirtulBox)

    阿里云服务器本月底到期了,之前自己犹豫不觉没上279元3年服务器的车,但是又要用linux,所以有了这篇文章. VirtulBox 一款开源的虚拟化主机应用,可以实现一台电脑上运行多个操作系统(Lin ...

  4. 19-关键字package和import

    1. package的使用1.1 使用说明: * 1.为了更好的实现项目中类的管理,提供包的概念 * 2.使用package声明类或接口所属的包,声明在源文件的首行 * 3.包,属于标识符,遵循标识符 ...

  5. [vue] computed 和 method

    计算属性 计算属性只有在它的相关依赖发生改变时才会重新取值 Method method每次渲染的时候都会被执行 举一个栗子 <template>...<div>  <p& ...

  6. 【学习笔记】ThreadLocal与引用类型相关知识点

    0 写在前边 今天以 "TheadLocal 为什么会导致内存泄漏" 为题与朋友们讨论了一波,引出了一些原理性的内容,本文就这个问题作答,并扩展相关的知识点 1 ThreadLoc ...

  7. Python最好IDE:Pycharm使用小技巧总结,让你写代码更为舒适

  8. 搭建MyBatis开发环境及基本的CURD

    目录 一.MyBatis概述 1. MyBatis 解决的主要问题 二.快速开始一个 MyBatis 1. 创建mysql数据库和表 2. 创建maven工程 3. 在pom.xml文件中添加信息 4 ...

  9. javascript逻辑运算与循环笔记

        短路运算(逻辑中断)     1.短路运算的原理:当有多个表达式(值)时,左边的表达式值可以确定结果的时候就不再继续运算右边的表达式的值     2.逻辑与 &&     如果 ...

  10. 比原链CTO James | Go语言成为区块链主流开发语言的四点理由

    11月24日,比原链CTO James参加了Go中国举办的Gopher Meetup杭州站活动,与来自阿里.网易的技术专家带来Kubernetes.区块链.日志采集.云原生等话题的分享.James向大 ...