• 中间件


    管道与中间件

    管道由中间件组成,可以想象成管道就是一个产品线的处理流程模板,中间件就是这个流程上需要对一件产品做的处理,而这个产品就是我们的请求,当我们的请求进入管道的时候,会按照中间件的顺序对于请求做处理,然后选择是否传至下一个中间件进行处理,直到到达管道末尾,然后返回结果。

    中间件#

    中间件作用:#

    微软官网原话:

    中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件

    • 选择是否将请求传递到管道中的下一个组件。
    • 可在管道中的下一个组件前后执行工作。

    请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。

    使用RunMap或者Use拓展方法来配置请求委托,也可将一个单独的请求委托并行指定为匿名方法(称为并行中间件),或在可重用的类中对其进行定义。 这些可重用的类和并行匿名方法即为中间件,也叫中间件组件。 请求管道中的每个中间件组件负责调用管道中的下一个组件,或使管道短路。 当中间件短路时,它被称为“终端中间件”,因为它阻止中间件进一步处理请求。

    简单来说,我们的请求处理管道其实是由一个个中间件组成的,每一个中间件都是一个处理请求和响应的组件(或者说委托,也就是RequestDelegate),每一个中间件处理完成后,会把HttpContext上下文(next)传递到下一个组件,直到到达终端中间件,终端中间件执行完成后,会原路返回。

    中间件短路:#

    中间件短路是指在这个中间件处理中,不会把HttpContext上下文传递给下一个组件,比如app.Run()就是一个终端中间件,他不会收到next,无论你自己定义几个app.Run(),永远在到达第一个终端中间件后,立即短路,不会调用后续的中间件。

    常见中间件及执行顺序#

    中间件一般摆放的顺序即完整请求处理管道:

    1. ExceptionHandler:异常处理
    2. UseHSTS:https
    3. UseHttpsRedirection:重定向
    4. UseStaticFiles:静态文件
    5. UseRouting:路由,因为6.0,应用继承了
    6. UseCORS:跨域
    7. UseAuthentication:身份认证
    8. UseAuthorization:鉴权
    9. 终端中间件

    并非所有中间件都完全按照此顺序出现,但许多中间件都会遵循此顺序。 例如

    • UseCorsUseAuthenticationUseAuthorization 必须按显示的顺序出现。
    • UseCors 当前必须在 UseResponseCaching 之前出现。
    • UseRequestLocalization 必须在可能检查请求区域性的任何中间件(例如 app.UseMvcWithDefaultRoute())之前出现。
    • 如果显示调用了UseRouting(),则后面必须紧跟UseEndpoint()中间件
    • 应尽早在管道中调用异常处理委托,这样它们就能捕获在管道的后期阶段发生的异常。

    想要了解更多内置中间件,[官网](ASP.NET Core 中间件 | Microsoft Learn)

    中间件的顺序对于安全性、性能和功能至关重要。

    在我们的Program.cs文件中,中间件执行的顺序就是以app.UseXXX()拓展方法从上至下执行,即Request请求,在到达终端中间件后,按原路返回,即从下至上执行则是Respones响应。所以每个中间件摆放的位置和,会影响http请求执行的顺序。

    举个例子,一般我们使用 app.UseStaticFiles()静态文件中间件会放在app.UseAuthentication()身份认证中间件前面,这样代表是公开访问。所以当请求需要在访问一些系统的一些静态文件的时候不会被拦截,比如系统的favicon图标、js文件等。

    中间件运行原理#

    Fun委托#

    单纯看类型就知道这是一个委托,传入下一个RequestDelegate委托、返回当前RequestDelegate委托处理的结果。

    RequestDelegate委托#

    微软的官方解释是

    一个可以处理 HTTP 请求的函数。

    在UseMiddleware()拓展方法中,我们自定义的中间件会被包装成为这个委托添加到管道中。

    RequestDelegate源码

    using System.Threading.Tasks;
    
    namespace Microsoft.AspNetCore.Http
    {
        /// 
        /// A一个可以处理 HTTP 请求的函数。
        /// 
        /// The  请求
        /// 表示请求处理完成的任务
        public delegate Task RequestDelegate(HttpContext context);
    }
    

    IApplicationBuilder接口#

    通过IApplicationBuilder接口,注册中间件,然后会在app.Run()的时候调用WebApplication.BuildRequestDelegation()方法(这个方法本质上就是调用的ApplicationBuilder.Builder()方法)把注册的中间件串联一起来作为请求管道。

    这个接口有一个实现类ApplicationBuilder。

    我们在应用的章节就知道了WebApplication类继承了这个接口,所以我们可以直接在应用上注册中间件。

    关键的属性和方法

    • 有一个存储请求中间件的列表
    • 有一个Use()方法用于把中间件添加到列表中
    • 有一个New()方法用来创造ApplicationBuilder,也就是管道分支
    • 有一个Builder()方法用于执行添加的中间件,并返回处理好的结果

    ApplicationBuilder实现类源码

    public class ApplicationBuilder : IApplicationBuilder
    {
        // 用于存储委托的列表
        private readonly List> _components = new();
        // 从ApplicationBuilder获取一组属性
        public IDictionary<string, object?> Properties { get; }
        // 添加委托到管道中
        public IApplicationBuilder Use(Func middleware)
        {
            _components.Add(middleware);
            return this;
        }
         // 创造当前的管道,也是管道分支。
         public IApplicationBuilder New()
         {
             return new ApplicationBuilder(this);
         }    
         // 用于执行添加的中间件,并返回处理好的结果,WebApplication.BuildRequestDelegation()本质上就是调用的这个方法来创建中间件委托
         public RequestDelegate Build()
            {
                // 注册一个中间件,默认请求上下文的结果是404
                RequestDelegate app = context =>
                {
                   // 如果我们到达管道的尽头,但我们有一个端点,那么就会发生一些意想不到的事情。
                   // 如果用户代码设置了终结点,但他们忘记添加 UseEndpoint 中间件,则可能会发生这种情况。
                    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
                    context.Response.StatusCode = StatusCodes.Status404NotFound;
                    return Task.CompletedTask;
                };
                // 这里循环需要注意的是。执行从列表索引的最后一个开始循环的,也就是默认把委托列表倒序执行,所以这里最先开始执行的是我们注册的最后一个中间件
                // 第一次的时候,先把最上面的定义的那个404中间件传入进去,获取最后一个中间件的结果。
                // 第二次的时候,把最后一个中间件的结果,放到倒数第二个中间件中,并获得执行的结果。
                // 第三次的时候,把倒数二个中间件的结果,放到倒数第三个中间件中,并获得结果。
                // 直到执行到第一个中间件。最终返回注册的第一个中间件的返回值。
                for (var c = _components.Count - 1; c >= 0; c--)
                {
                    app = _components[c](app);
                }
    
                return app;
            }
    }
    

    自定义中间件#

    有2种方式:

    1. 继承IMiddleware接口需要满足的条件:

      注册实现的中间件类,必须通过依赖注入到容器中,否则会报错,原因是,在UseMiddleware()的时候,这种方式实现的中间件类,会调用内部的UseMiddlewareInterface()从依赖注入容器中获取服务实例。

    2. 自定义中间件需要满足的条件:

      必须是public修饰,含有一个参数为RequestDelegate类型的构造函数,

      必须包含一个public修饰的,名称为Invoke或者InvokeAsync的实例异步方法,且返回类型为Task,包含一个类型为HttpContext的参数。

      无需注入,会通过反射创造实例.

    自定义容错中间件demo

    // 这个自定义中间件的作用是,当我们的程序在请求过程中抛出了一些内部异常,提示500,则可以捕捉并进行友好捕捉返回。
    public class ErrorMiddleware
    {
      public readonly RequestDelegate _next;
      // 构造函数会尝试从依赖注入容器中获取服务
      public ErrorMiddleware (RequestDelegate next)
      {
         _next = next;
      }
        
      public async Task InvokeAsync(HttpContext context)
      {
          try
          {
              // 把请求传递给下一个中间件
              awite _next.Invoke(context);         
          }
          catch(Exception ex)
          {
              // 这里可以执行记录日志等操作
              // 日志记录方法。。。。。
              
              // 友好包装
              // 调用ConfigureAwait,可以在异步执行的时候不捕获上下文,意思是如果你的异步方法后不涉及上下文操作,调用这个可以减少性能开销
                 await WriteExceptionAsync(context, ex).ConfigureAwait(false);
              
          }      
      }
          /// 
            /// 友好返回处理
            /// 
            /// 
            /// 
            /// 
        private async Task WriteExceptionAsync((HttpContext context, Exception exception)
        {
            if (exception.IsNull()) return;
            // 自定义友好返回类
            ServiceResult result = new ServiceResult();
             // 返回友好的提示
                HttpResponse response = context.Response;
                response.ContentType = context.Request.Headers["Accept"];
                response.ContentType = "application/json";
                result.IsFailure(exception.Message);   
            // 写入返回信息中
                await response.WriteAsync(result.ToJson()).ConfigureAwait(false);
        }
    }
    
    

    注册自定义中间件#

    因为定义的中间件有2种方式,一个是通过继承IMiddlewar接口实现,一个是按照约定格式实现的。他们都是通过使用的ApplicationBuilder的拓展方法UseMiddleware()方法来把中间件注册到管道中。

    不过在UseMiddleware()方法内部实现其实是2种:

    • 通过接口实现的,会调用私有的UseMiddlewareInterface()方法。
    • 按照约定格式实现的,调用UseMiddleware()方法。

    他们的区别就在

    • UseMiddlewareInterface()方法会从依赖注入容器中获取服务实例(毕竟是通过接口实现的)然后调用InvokeAsync()方法,
    • UseMiddleware()则是通过反射创造中间件委托表达式,然后调用执行返回。不过无论是接口实现还是约定实现,都可以调用UseMiddleware()方法来把中间件注册到管道中。

    注意!!!!!

    • 如果是手写中间件,则Invoke()方法的参数不要带requestdelegation。否则会产生依赖注入容器报错,
    • 如果继承IMiddleware,内部会调用UseMiddlewareInterface()从依赖注入容器中获取实例,如果使用了AutoFac替换掉了依赖注入容器,那么就需要手动注册,

    UseMiddleware#

    调用中间件

     app.UseMiddleware();
    

    UseMiddleware源码

    public static class UseMiddlewareExtensions
    {
        // 这里就是为什么即使没有继承IMiddleware接口,方法可以定义为Invoke的原因。
        internal const string InvokeMethodName = "Invoke";
        internal const string InvokeAsyncMethodName = "InvokeAsync";
    
        private static readonly MethodInfo GetServiceInfo = typeof(UseMiddlewareExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static)!;                                                                
        // 我们将把所有公共构造函数和公共方法保留在中间件上
        private const DynamicallyAccessedMemberTypes MiddlewareAccessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods;
    
           /// 
           /// 添加中间件到管道里面
           /// 
           /// The  instance.
           /// 中间件
           /// 要传递给中间件类型实例的构造函数的参数。
        public static IApplicationBuilder UseMiddleware<[DynamicallyAccessedMembers(MiddlewareAccessibility)]TMiddleware>(this IApplicationBuilder app, params object?[] args)
        {
            return app.UseMiddleware(typeof(TMiddleware), args);
        }
           /// 
           /// 自定义中间件类,添加中间件到管道里面
           /// 
           /// The  instance.
           /// 中间件
           /// 要传递给中间件类型实例的构造函数的参数。    
        public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, [DynamicallyAccessedMembers(MiddlewareAccessibility)] Type middleware, params object?[] args)
        {
            // 如果是继承IMiddleware实现的。
            if (typeof(IMiddleware).IsAssignableFrom(middleware))
            {
                // 如果继承的IMiddleware,不允许从构造函数中传递参数
                if (args.Length > 0)
                {
                    throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
                }
                // 从下方的UseMiddlewareInterface工厂来生成委托
                return UseMiddlewareInterface(app, middleware);
            }
            // 否则进入自定义中间件的解析流程
            // 获取依赖注入容器
            var applicationServices = app.ApplicationServices;
            // 添加到委托链表中
            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();
                               // 如果定义Invoke或者InvokeAsync超过1个则抛出异常
                               if (invokeMethods.Length > 1)
                               {
                                   throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
                               }
                                // 如果没有定义Invoke或者InvokeAsync方法则抛出异常
                               if (invokeMethods.Length == 0)
                               {
                                   throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
                               }
                               // 获取定义好的第一个Invoke或者InvokeAsync方法
                               var methodInfo = invokeMethods[0];
                               // 如果这个方法的返回值不是Task则抛出异常
                               if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
                               {
                                   throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
                               }
                               // 获取方法的参数列表
                               var parameters = methodInfo.GetParameters();
                               // 如果没有定义参数,或者第一个参数不是HttpContext则抛出异常
                               if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
                               {
                                   throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
                               }
                               // 获取构造函数中传递的参数
                               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);
                               // 如果参数只有一个,(invoker默认的参数必须有一个HttpContext)
                               if (parameters.Length == 1)
                               {  
                                   // 则根据该中间件实例创造一个委托
                                   return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance);
                               }
                               // 如果方法参数超过1个,则代表有自己依赖注入的方法。
                               // 把这个Invoke或者InvokeAsync方法编译成一个可以执行的Lambda表达式委托
                               var factory = Compile(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);
                               };
                           });
        }
           /// 
           /// 如果继承的是IMiddleware的中间件,则调用的此方法添加中间件到管道里面
           /// 
           /// The  instance.
           /// 中间件类型
        private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type middlewareType)
        {
            // 往中间件列表中添加一个委托
            return app.Use(next =>
                           {
                               // 解析
                               return async context =>
                               {
                                   // 从依赖注入容器中获取IMiddlewareFactory工厂
                                   // context.RequestServices.GetService,我们之前在依赖注入章节讲过,调用这个方法其实就是向WebApplication.ServiceProvider获取服务。
                                   var middlewareFactory = (IMiddlewareFactory?)context.RequestServices.GetService(typeof(IMiddlewareFactory));
                                   // 如果没有实现这个工厂,则报错
                                   if (middlewareFactory == null)
                                   {
                                       // No middleware factory
                                       throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)));
                                   }
                                   // 根据这个工厂创造中间件的实例
                                   var middleware = middlewareFactory.Create(middlewareType);
                                   // 创造失败
                                   if (middleware == null)
                                   {
                                       // 抛出异常
                                       // The factory returned null, it's a broken implementation
                                       throw new InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(), middlewareType));
                                   }
                                   try
                                   {
                                       // 调用实现的InvokeAsync方法
                                       await middleware.InvokeAsync(context, next);
                                   }
                                   finally
                                   {
                                       // 释放中间件
                                       middlewareFactory.Release(middleware);
                                   }
                               };
                           });
        }
    }
    
    

    管道分支#

    什么是管道分支,按照正常的请求,我们每个请求都是走的同一个管道,同一个模板的处理方式,那么如果我们需要对一个请求,比如有一个关于处理用户信息请求,在到了主管道的特定中间件,我需要对于这个请求做一些特殊处理,就可以用到管道分支这个操作。

    Map#

    特点:使用拓展方法app.Map("匹配的路由",处理方法):根据请求路径HttpRequest.Path中的值匹配项来创建请求管道分支。 如果请求路径以给定路径开头,则执行分支。分支内还可以嵌套分支。当请求进入到管道分支后,执行完成不会再回到主管道!!!!,所以必须在处理方法中,调用App.Run()来终结请求。

    MapWhen#

    特点:使用拓展方法app.MapWhen(context => 表达式,处理方法):根据表达式的值来判断是否需要创建管道分支,当请求进入到管道分支后,执行完成不会再回到主管道!!!!,所以必须在处理方法中,调用App.Run()来终结请求。

    UseWhen#

    特点:会回到主管道

    请求是如何进入管道、中间件什么时候被执行#

  • 相关阅读:
    UE4 TextRender显示中文方法
    ssm毕设项目学生信息管理系统63yq0(java+VUE+Mybatis+Maven+Mysql+sprnig)
    Android入门第1天-Android Studio的安装
    【功能实现】登录模块 - 学习/实践
    CharacterEncodingFilter的用法
    多线程 CompletableFuture(2)
    寻找技术合伙人111
    微信小程序第三天
    氨基染料研究:Lumiprobe FAM 胺,6-异构体
    报道 | 2023-2024年1月国际运筹优化会议汇总
  • 原文地址:https://www.cnblogs.com/boise/p/18002742