ASP.NET Core管道深度剖析[共4篇]

 

在《管道是如何处理HTTP请求的?》中,我们对ASP.NET Core的请求处理管道的构成以及它对请求的处理流程进行了详细介绍,接下来我们需要了解的是这样一个管道是如何被构建起来的。这样一个管道由一个服务器和一个HttpApplication构成,前者负责监听请求并将接收的请求传递给给HttpAppkication对象处理,后者则将请求处理任务委托给注册的中间件来完成。中间件的注册是通过ApplicationBuilder对象来完成的,所以我们先来了解一下这究竟是个怎样的对象。

目录
ApplicationBuilder
StartupLoader
WebHost
WeHostBuilder
总结

一、ApplicationBuilder

我们所说的ApplicationBuilder是对所有实现了IApplicationBuilder接口的所有类型及其对象的统称。注册到WebHostBuilder上的启动类型具有一个用于管道定值的Configure方法,它利用作为参数的ApplicationBuilder对象进行中间件的注册。由于ApplicationBuilder与组成管道的中间件具有直接的关系,所以我们得先来说说中间件在管道中究竟体现为一个怎样的对象。

中间件在请求处理流程中体现为一个类型为Func<RequestDelegate,RequestDelegate>的委托对象,对于很多刚刚接触请求处理管道的读者朋友们来说,可能一开始对此有点难以理解,所以容来略作解释。我们上面已经提到过RequestDelegate这么一个委托,它相当于一个Func<HttpContext, Task>对象,该委托对象表示针对提供的HttpContext所做进行一项处理操作,这项操作代表某个中间件针对请求的处理。那为何我们不直接用一个RequestDelegate对象来表示一个中间件,而将它表示成一个Func<RequestDelegate,RequestDelegate>对象呢?

clip_image002

在大部分应用中,我们会针对具体的请求处理需求注册多个不同的中间件,这些中间件按照注册时间的先后顺序进行排列进而构成我们所谓的请求处理管道。对于某个中间件来说,在它完成了自身的请求处理任务之后,需要将请求传递给下一个中间件作后续的处理。Func<RequestDelegate,RequestDelegate>中作为输入参数的RequestDelegate对象代表一个委托链,体现了后续中间件对请求的处理,当前中间件将自身实现的请求处理任务添加到这个委托链中,而返回RequestDelegate对象代表最新的委托链。

以右图所示的管道为例,如果用一个Func<RequestDelegate,RequestDelegate>来表示中间件B,那么作为输入参数的RequestDelegate对象代表的是C对请求的处理操作,而返回值则代表B和C先后对请求处的处理操作。如果一个Func<RequestDelegate,RequestDelegate>代表第一个从服务器接收请求的中间件(比如A),那么执行该委托对象返回的RequestDelegate实际上体现了整个管道对请求的处理。

在对中间件有了充分的了解之后,我们来看看用于注册中间件的IApplicationBuilder接口的定义。如下所示的是经过裁剪后的IApplicationBuilder接口的定义,我们只保留了两个核心的方法,其中Use方法实现了针对中间件的注册,另一个Build方法则将所有注册的中间件转换成一个RequestDelegate对象。

   1: public interface IApplicationBuilder
   2: {
   3:     RequestDelegate Build();
   4:     IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
   5: }

从编程便利性考虑,很多预定义的中间件都具有用于注册的扩展方法,比如我们调用扩展方法UseStaticFiles来注册处理静态文件请求的中间件。对于我们演示的发布图片的应用来说,它也是通过调用一个具有如下定义的扩展方法UseImages来注册处理图片请求的中间件。

   1: public static class ApplicationBuilderExtensions
   2: {
   3:     public static IApplicationBuilder UseImages(this IApplicationBuilder app, string directory)
   4:     {
   5:         Func<RequestDelegate, RequestDelegate> middleware = next =>
   6:         {
   7:             return context =>
   8:             {
   9:                 string fileName = context.Request.Url.LocalPath.TrimStart('/');
  10:                 if (string.IsNullOrEmpty(Path.GetExtension(fileName)))
  11:                 {
  12:                     fileName += ".jpg";
  13:                 }
  14:                 fileName = Path.Combine(directory, fileName);
  15:                 context.Response.WriteFile(fileName, "image/jpg");
  16:                 return next(context);
  17:             };
  18:          };
  19:         return app.Use(middleware);
  20:     }
  21: }

ASP.NET Core默认使用的是一个类型为ApplicationBuilder的对象来注册中间件,我们采用如下的代码片断来模拟它的实现逻辑。我们采用一个List<Func<RequestDelegate, RequestDelegate>>对象来存放所有注册的中间件,并调用Aggregate方法将它转换成一个RequestDelegate对象。

   1: public class ApplicationBuilder : IApplicationBuilder
   2: {
   3:     private IList<Func<RequestDelegate, RequestDelegate>> middlewares = new List<Func<RequestDelegate, RequestDelegate>>();  
   4:  
   5:     public RequestDelegate Build()
   6:     {
   7:         RequestDelegate seed = context => Task.Run(() => {});
   8:         return middlewares.Reverse().Aggregate(seed, (next, current) => current(next));
   9:     }    
  10:  
  11:     public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
  12:     {
  13:         middlewares.Add(middleware);
  14:         return this;
  15:     }
  16: }

ASP.NET Core并不会直接创建ApplicationBuilder对象来注册中间件,而是利用对应的工厂来创建它。创建爱你ApplicationBuilder的工厂通过接口IApplicationBuilderFactory表示,在模拟的管道中我们将这个接口简化成如下的形式,该接口的默认实现者ApplicationBuilderFactory会直接创建一个ApplicationBuilder类型的对象。

   1: public interface IApplicationBuilderFactory
   2: {
   3:     IApplicationBuilder CreateBuilder();
   4: }
   5:  
   6: public class ApplicationBuilderFactory : IApplicationBuilderFactory
   7: {
   8:     public IApplicationBuilder CreateBuilder()
   9:     {
  10:         return new ApplicationBuilder();
  11:     }
  12: }


二、StartupLoader

一个服务器和一组中间件组成了ASP .NET Core的HTTP请求处理管道,中间件的注册通过调用ApplicationBuilder的Use方法来完成,而这一切实现在注册为启动类型的Configure方法中,我们可以将针对这个方法的调用抽象成一个类型为Action <IApplicationBuilder> 的委托对象。在管道初始化过程中,WebHost必须获取并执行这个委托以完成中间件的注册工作。具体来说这个委托对象的获取是利用一个名为StatupLoader对象来完成的。

这里的StartupLoader是对所有实现了IStartupLoader接口的所有类型机器对象的统称,我们在模拟管道中将这个接口作了如下所示的简化。IStartupLoader接口具有的唯一方法GetConfigureDelegate根据指定的启动类型生成一个Action <IApplicationBuilder> 。对于默认实现该接口的StartupLoader类来说,它的GetConfigureDelegate方法返回的委托会以反射的方式执行定义在指定启动类型的Configure方法。简单起见,我们假设这个Configure方法为实例方法,启动对象可以直接调用默认无参构造函数来创建。

   1: public interface IStartupLoader
   2: {
   3:     Action<IApplicationBuilder> GetConfigureDelegate(Type startupType);
   4: }
   5:  
   6: public class StartupLoader : IStartupLoader
   7: {
   8:     public Action<IApplicationBuilder> GetConfigureDelegate(Type startupType)
   9:         => app => startupType.GetMethod("Configure").Invoke(Activator.CreateInstance(startupType), new object[] { app });
  10: }


三、WebHost

ASP.NET Core的请求处理管道是由作为应用宿主的WebHost对象创建出来的,后者是对所有实现了IWebHost接口的所有类型及其对象的统称,我们在模拟管道中将这个接口作了如下的简化,仅仅保留了唯一的方法Start。随着WebHost因Start方法的调用而被开启,整个管道也随之被建立起来。

   1: public interface IWebHost
   2: {
   3:     void Start();
   4: }

通过上面的介绍我们知道请求处理管道可以理解为一个服务器和一个HttpApplication的组合,当我们创建出一个服务器并指定一个具体的HttpApplication对象调用其Start方法将其启动时,这个管道就被建立起来。服务器的创建是利用ServerFactory来完成的,而默认采用的HttpApplication类型为HostingApplication。

当我们创建一个HostingApplication对象的时候,需要指定一个类型为RequestDelegate的委托对象,后者通过调用ApplicationBuilder的Build方法获得,代表了所有注册的中间件针对当前请求的处理。所以HostingApplication的创建需要一个ApplicationBuilder对象,这个对象通过ApplicationBuilderFactory来创建。在调用ApplicationBuilder的Build方法将注册的中间件转换成RequestDelegate委托之前,需要完成针对中间件的注册工作。实现在启动类型的Configure方法中针对中间件的注册可以体现为一个Action <IApplicationBuilder>对象,这对委托对象可以通过StartupLoader来获取。

综上所述,为了创建并启动一个服务器,WebHost至少需要一个ServerFactory和ApplicationBuilderFactory来创建服务器和ApplicationBuilder,还需要一个StartupLoader来最终完成对中间件的注册。除此之外,还需要知道注册到WebHostBuilder上的启动类型。由于依赖注入被广泛应用到了ASP.NET Core的请求处理管道中,对于前面三个对象,会先以服务的形式注册到DI容器中,那么WebHost只需要利用ServiceProvider对象根据对应的服务接口得到这三个对象。

   1: public class WebHost : IWebHost
   2: {
   3:     private IServiceProvider     serviceProvider;
   4:     private Type                 startupType;
   5:  
   6:     public WebHost(IServiceCollection appServices, Type startupType)
   7:     {
   8:         this.serviceProvider     = appServices.BuildServiceProvider();
   9:         this.startupType         = startupType;
  10:     }        
  11:  
  12:     public void Start()
  13:     {
  14:         IApplicationBuilder applicationBuilder = serviceProvider.GetRequiredService<IApplicationBuilderFactory>().CreateBuilder();
  15:         serviceProvider.GetRequiredService<IStartupLoader>().GetConfigureDelegate(startupType)(applicationBuilder);    
  16:         IServer server = serviceProvider.GetRequiredService<IServerFactory>().CreateServer();
  17:         server.Start(new HostingApplication(applicationBuilder.Build()));
  18:     }
  19: }

由上面代码片段提供的这个极简版的WebHost类通过构造函数的参数提供包含原始服务注册的ServiceCollection对象和启动类型,我们利用前者创建对应的ServiceProvider。在Start方法中,我们利用ServiceProvider得到一个ApplicationBuilder对象和一个StartupLoader对象。我们将启动类型作为参数调用StartupLoader的GetConfigureDelegate方法得到一个Action<IApplicationBuilder>对象。接下来,我们将ApplicationBuilder对象作为参数调用这个Action<IApplicationBuilder>委托对象,后者会执行定义在启动类型中的Configure方法并最终完整对中间件的注册。

在这之后,我们利用ServiceProvider得到一个ServiceFactory对象并利用它创建出代码服务器的Server对象。为了调用其Start方法,我们需要创建一个HostingApplication对象作为参数,而后者的创建需要一个代表所有中间件针对当前请求进行处理的RequestDelegate对象,这个对象直接通过调用ApplicationBuilder对象的Build方法得到。当服务器因Start方法的调用而被启动后,整个请求处理管道被正式建立起来。

四、WebHostBuilder

作为应用宿主的WebHost创建了ASP.NET Core的请求处理管道,而WebHost又是由它的工厂WebHostBuilder创建的。WebHostBuilder是对所有实现了IWebHostBuilder接口的所有类型及其对象的统称,我们在模拟管道中对这个接口做了极大的简化,仅仅保留了如下面代码片段所示的三个方法成员。针对WebHost的创建通过Build方法实现,额外两个方法(UseStartup和UseServer)分别用于注册启动类型和用于创建服务器的ServerFactory。

   1: public interface IWebHostBuilder
   2: {
   3:     IWebHostBuilder UseStartup(Type startupType);
   4:     IWebHostBuilder UseServer(IServerFactory factory);
   5:     IWebHost Build();
   6: }

依赖注入在ASP.NET Core 请求处理管道中得到了极大的应用,创建WebHost提供的ServiceCollection对象最初由WebHostBuilder提供。WebHost在构建管道时使用的一系列服务对象(ApplicationBuilderFactory和StartupLoader)最初都由WebHostBuilder注册到这个ServiceCollection对象中,这一切都体现如下所示的这个默认使用的WebHostBuilder类型中。

   1: public class WebHostBuilder : IWebHostBuilder
   2: {
   3:     private Type                   startupType;
   4:     private IServiceCollection     services;
   5:  
   6:     public WebHostBuilder()
   7:     {
   8:         services = new ServiceCollection()
   9:             .AddTransient<IStartupLoader, StartupLoader>()
  10:             .AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
  11:     }
  12:  
  13:     public IWebHost Build() => new WebHost(services, this.startupType);
  14:  
  15:     public IWebHostBuilder UseServer(IServerFactory factory)
  16:     {
  17:         services.AddSingleton<IServerFactory>(factory);
  18:         return this;
  19:     }   
  20:      
  21:     public IWebHostBuilder UseStartup(Type startupType)
  22:     {
  23:         this.startupType = startupType;
  24:         return this;
  25:     }
  26: }


五、总结

clip_image004综上所述,我们已经对ASP.NET Core应用如何利用WebHostBuilder最终构建出请求处理管道的流程以及管道自身处理请求的流程具有了一定的了解,现在我们来做一个简单的总结。请求处理管道涉及到四个核心的对象,它们分别是WebHostBuilder、WebHost、Server和HttpApplication,它们之间具有如图11所示的关系。我们通过WebHostBuilder来创建WebHost,并领用后者来构建请求处理管道。

请求处理管道通过一个Server和一个HttpApplication对象组成,后者是对所有注册的中间件的封装。当WebHost被启动的时候,它会创建Server和HttpApplication对象,并将后者作为参数调用Server的Start方法以启动服务器。启动后的Server开启监听请求并利用HttpApplication来处理接收到请求。当HttpApplication完成了所有请求处理工作之后,它会利用Server完成对请求的最终响应。

上面所述的所有内容都是针对我们自定义的模拟管道来介绍的,虽然我们对这个模拟管道做了极大的简化,但是它依然体现了ASP.NET Core管道处理请求的真实流程,而且真实管道的创建方式也与模拟管道基本一致。如果读者朋友们能够对这个模拟管道具有深刻的理解,我相信对真实管道的把握就会变得非常容易。

 

 


一、采用管道处理HTTP请求
二、创建一个“迷你版”的管道来模拟真实管道请求处理流程 
三、管道如何处理HTTP请求的
四、管道是如何被创建出来的

 

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

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

相关文章

SQL为什么动不动就百行以K记?

发明SQL的初衷之一显然是为了降低人们实施数据查询计算的难度。SQL中用了不少类英语的词汇和语法&#xff0c;这是希望非技术人员也能掌握。确实&#xff0c;简单的SQL可以当作英语阅读&#xff0c;即使没有程序设计经验的人也能运用。 然而&#xff0c;面对稍稍复杂的查询计算…

深入理解happens-before和as-if-serial语义

本文大部分整理自《Java并发编程的艺术》&#xff0c;温故而知新&#xff0c;加深对基础的理解程度。下面可以和小编来一起学习下 概述 本文大部分整理自《Java并发编程的艺术》&#xff0c;温故而知新&#xff0c;加深对基础的理解程度。 指令序列的重排序 我们在编写代码…

开源SPL重新定义OLAP Server

OLAP&#xff08;Online Analytical Processing&#xff09;是指在线联机分析&#xff0c;基于数据查询计算并实时获得返回结果。日常业务中的报表、数据查询、多维分析等一切需要即时返回结果的数据查询任务都属于OLAP的范畴。对应的&#xff0c;行业内也有相应产品来满足这类…

平层、错层、跃层、复式、loft的区别是什么?

平层正如字面上的理解&#xff0c;是所有功能厅都在同一个水平面上。平时我们所见的户型&#xff0c;都是平层。错层室内各功能用房在不同的平面上&#xff0c;用2-4个台阶进行隔断。跃层是两层的住宅&#xff0c;在室内会设计一条楼梯连接上下两层&#xff0c;功能区会分开。复…

C#集合类型总结和性能分析

C#集合类型概述 集合是.NET FCL(Framework Class Library)中很重要的一部分。所有的集合类都继承自IEnumerable。集合类总体可分为一下几类&#xff1a;关联/非关联型集合&#xff0c;顺序/随机访问集合&#xff0c;顺序/无序集合&#xff0c;泛型/非泛型集合&#xff0c;线程…

Spring AOP(通知、连接点、切点、切面)

一、AOP术语 通知&#xff08;Advice&#xff09;   切面的工作被称为通知。通知定义了切面是什么以及何时使用。除了描述切面要完成的工作&#xff0c;通知还解决了何时执行这个工作的问题。 5种通知类型&#xff1a;前置通知&#xff08;Before&#xff09;&#xff1a;在…

C#中几种常用的集合的用法

集合:将一推数据类型相同的数据放入到一个容器内&#xff0c;该容器就是数组&#xff1a;内存中开辟的一连串空间。 非泛型集合 ArrayList集合&#xff1a; ArrayList是基于数组实现的&#xff0c;是一个动态数组&#xff0c;其容量能自动 增长 ArrayList的命名空间System.…

C#使用Redis的基本操作

一&#xff0c;引入dll 1.ServiceStack.Common.dll 2.ServiceStack.Interfaces.dll 3.ServiceStack.Redis.dll 4.ServiceStack.Text.dll 二&#xff0c;修改配置文件 在你的配置文件中加入如下的代码&#xff1a; <appSettings><add key"RedisPath" value…

Navicat将mysql表结构导成oracle表结构

1&#xff0c;选中对应的表右键逆向表到模型 2.点击右上角文件转换模型为 3.模型选择物理&#xff0c;数据库oracle&#xff0c;选择对应的版本 4.新弹出的模型点击右上角文件&#xff0c;导出sql 5.选择路径导出sql

程序员们的三高:高并发、高性能、高可用

你们知道淘宝&#xff0c;京东这些购物商场吗&#xff1f;他们到了双11&#xff0c;双12为什么能支持全国14亿人口同时购物下单呢&#xff0c;因为他们的程序做到了高并发、高性能、高可用。那么你对程序员的三高了解多少呢&#xff1f; 高并发 一. 高并发 高并发是现在互联…

char 和 varchar 的区别,数据库索引B+树

char 和 varchar 的区别 char(n) &#xff1a;固定长度&#xff0c;效率高&#xff1b;缺点&#xff1a;占用空间&#xff1b;存储固定长度的&#xff0c;使用 char 非常合适。 varchar(n) &#xff1a;可变长度&#xff0c;存储的值是每个值占用的字节再加上一个用来记录其长…

C#基础操作符详解

本节内容&#xff1a; 1.操作符概览&#xff1b; 2.操作符的本质&#xff1b; 3.操作符与运算顺序 4.操作符详解。 1.操作符概览&#xff1a; 操作符&#xff08;Operator&#xff09;也译为”运算符” 操作符是用来操作数据的&#xff0c;被操作符操作的数据称为操作数&a…

C# 有什么惊艳到你的地方?

作者&#xff1a;皮皮关 链接&#xff1a;https://www.zhihu.com/question/335137780/answer/786853293 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 很多游戏开发者都是由于Unity而“被迫”使用C#的。但用过一段…

SqlServer学习之存储过程

前言&#xff1a;对于存储过程一直有一种抵触的心理&#xff0c;因为毕业至今所在的公司开发组都不是很规范&#xff0c;对于开发的一些注意事项并没有很多的规定&#xff0c;只是在知乎上查找相关知识的时候&#xff0c;看到很多人对于在程序里使用存储过程的不好之处都有很多…

中间件之RPC

一、RPC的定义 1、RPC(Romote Procedure Call)&#xff1a;远程过程调用&#xff0c;允许一台计算机程序远程调用另外一台计算机的子程序&#xff0c;不用关心底层网络通信 2、应用&#xff1a;分布式网络通信 3、在Socket的基础上实现&#xff0c;比socket需要更多资源 4、…

需求分析之UML用例图学习

用例图常用的三种关系浅析&#xff1a; &#xff08;一&#xff09;泛化(Inheritance) 通常理解的继承关系 &#xff08;二&#xff09;包含&#xff08;include&#xff09; 分解功能&#xff0c;一定包含的功能 &#xff08;三&#xff09;拓展(extend) 附加功能&#xff0c…

UML之用例图(use case)箭头方向

1、Association&#xff0c;无箭头&#xff0c;Actor连接UseCase即可&#xff1b; 2、DirectedAssocition&#xff0c;Actor连接UseCase&#xff0c;箭头由Actor指向UseCase&#xff08;角色指向用例&#xff09;&#xff1b; 3、Generalization&#xff0c;继承&#xff0c;…

UML-记忆技巧

箭头方向 UML箭头方向&#xff1a;从子类指向父类&#xff0c;读作继承自定义子类时需要通过extend关键字指定父类子类一定时知道父类定义的&#xff0c;但父类并不知道子类的定义 只有知道对方信息时才能指向对方 image.png 实现-继承|虚线-实现 空心三角箭头&#xff1…

国密算法SM2,SM3,SM4-java实现

SM2是国家密码管理局于2010年12月17日发布的椭圆曲线公钥密码算法&#xff0c;基于ECC。其签名速度与秘钥生成速度都快于RSA&#xff0c;非对称加密&#xff0c;该算法已公开 SM3是中华人民共和国政府采用的一种密码散列函数标准&#xff0c;由国家密码管理局于2010年12月17日…

数据库事务隔离级别-- 脏读、幻读、不可重复读(清晰解释)

一、数据库事务隔离级别 数据库事务的隔离级别有4个&#xff0c;由低到高依次为Read uncommitted 、Read committed 、Repeatable read 、Serializable &#xff0c;这四个级别可以逐个解决脏读 、不可重复读 、幻读 这几类问题。 √: 可能出现 : 不会出现 脏读不可重复读幻…