zoukankan      html  css  js  c++  java
  • 跟我一起学.NetCore之中间件(Middleware)简介和解析请求管道构建

    前言

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

    正文

    那中间件是那路大神?

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

    img

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

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

    img

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

    img

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

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

    img

    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综艺圈",识别关注跟我一起学~~~

    image-20200903102501781

  • 相关阅读:
    基于ArcGIS10.0和Oracle10g的空间数据管理平台十一(C#开发)空间数据字段检查
    IT技术人生路之我的大学网站开发技术团队
    分布式日志收集系统: Facebook Scribe
    基于ArcGIS10.0和Oracle10g的空间数据管理平台十(C#开发)空间数据导入RDBMS上MDB格式
    IT技术人生路之我的大学初入大学及军训
    IT技术人生路之我的大学我技术方向的转变
    基于ArcGIS10.0和Oracle10g的空间数据管理平台十三(C#开发)空间数据导出
    基于ArcGIS10.0和Oracle10g的空间数据管理平台(C#开发)系统需求分析
    web服务
    js数据转换
  • 原文地址:https://www.cnblogs.com/zoe-zyq/p/13606276.html
Copyright © 2011-2022 走看看