管道是如何建立起来的?

管道是如何建立起来的?

在《管道是如何处理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>对象呢?

在大部分应用中,我们会针对具体的请求处理需求注册多个不同的中间件,这些中间件按照注册时间的先后顺序进行排列进而构成我们所谓的请求处理管道。对于某个中间件来说,在它完成了自身的请求处理任务之后,需要将请求传递给下一个中间件作后续的处理。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: }

五、总结

综上所述,我们已经对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请求的
四、管道是如何被创建出来的

时间: 2024-10-25 15:04:44

管道是如何建立起来的?的相关文章

ASP.NET Core管道深度剖析(4):管道是如何建立起来的?

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 在<管道是如何处理HTTP请求的?>中,我们对ASP.NET Core的请求处理管道的构成以及它对请求的处理流程进行了详细介绍,接下来我们需要了解的是这样一个管道是如何被构建起来的.这样一个管道由一个服务器和一个HttpApplication

【翻译】MongoDB指南/聚合——聚合管道

[原文地址]https://docs.mongodb.com/manual/ 聚合 聚合操作处理数据记录并返回计算后的结果.聚合操作将多个文档分组,并能对已分组的数据执行一系列操作而返回单一结果.MongoDB提供了三种执行聚合的方式:聚合管道,map-reduce方法和单一目的聚合操作. 聚合管道 MongoDB的聚合框架模型建立在数据处理管道这一概念的基础之上.文档进入多阶段管道中,管道将文档转换为聚合结果.最基本的管道阶段类似于查询过滤器和修改输出文档形式的文档转换器. 其他的管道为分组和

采用管道处理HTTP请求

采用管道处理HTTP请求 之所以称ASP.NET Core是一个Web开发平台,源于它具有一个极具扩展性的请求处理管道,我们可以通过这个管道的定制来满足各种场景下的HTTP处理需求.ASP. NET Core应用的很多特性,比如路由.认证.会话.缓存等,也同时定制消息处理管道来实现的.我们甚至可以通过管道定制在ASP.NET Core平台上创建我们自己的Web框架,实际上MVC和SingalR这两个重要的Web框架也是采用这样的方式创建的. HTTP协议自身的特性决定了任何一个Web应用的工作方

ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求

之所以称ASP.NET Core是一个Web开发平台,源于它具有一个极具扩展性的请求处理管道,我们可以通过这个管道的定制来满足各种场景下的HTTP处理需求.ASP. NET Core应用的很多特性,比如路由.认证.会话.缓存等,也同时定制消息处理管道来实现的.我们甚至可以通过管道定制在ASP.NET Core平台上创建我们自己的Web框架,实际上MVC和SingalR这两个重要的Web框架也是采用这样的方式创建的. HTTP协议自身的特性决定了任何一个Web应用的工作方式都是监听.接收并处理HT

化工管道仿真技术PipelineStudio v3.4.0.0 Final-ISO 1CD稳定版

化工管道仿真技术PipelineStudio v3.4.0.0 Final-ISO 1CD稳定版 PipelineStudio(管道工作室)还是倍受好评的操作人员培训资源.他们不必进入控制室即可观察到脱机环境中操作变化所带来的液压效果.他们可以学习如何预测不同操作在管道上运行的结果,从而全面提高操作的质量并且削减昂贵的维护费用. FTI.FastBlank.2014.for.ProEGEOMAGIC.DESIGN.X.V5.1.WIN64GEOMAGIC.FOUNDATION.V2014.3.W

rm与管道使用

一 问题初始:用通常意义的管道使用这样可以:(1)ls -l | sed -n '/~$/p' 我用显示出系统自己建立的备份文件这时,我想删除这些文件,我仍然使用了管道,并执行了以下命令(2)ls -l | sed -n '/~$/p' | rm -rf 然后又用(1)中的命令显示,结果完全未变,即未删除任何文件上互联网上查解,应如下使用:    ls -l | sed -n '/~$/p' | xargs rm -rf执行,则结果与预期相同,即删除了所以生成的备份文件 当然此问题还可以这样解:

SQL Server 连接问题-命名管道

原文:SQL Server 连接问题-命名管道 出自:http://blogs.msdn.com/b/apgcdsd/archive/2011/01/12/sql-server-1.aspx 一.前言 在使用SQL Server 的过程中,用户遇到的最多的莫过于连接问题了.本文将深度讨论SQL Server 连接问题的方方面面,希望能帮你彻底解决SQL server 的连接问题. SQL Server 支持的通讯协议很多,如命名管道(Named Pipes).TCP/IP 套接字.共享内存(Sh

【Java】线程管道通讯

很多操作系统对于管道的吹嘘往往是天花龙凤, 好心点的就贴段伪代码给你看,为出书而出书的,就直接一堆概念堆在上面,让人根本看不懂, 如此简单的概念,明明几句话就解释清楚,有的书还专门开出一章来讨论这个问题,完全没有必要! 一.基本概念 其实管道的概念非常简单,就是连接两个线程通讯的缓冲区,画个图就更加明白了 写者进程把自己的数据通过管道输出流写入管道,读者进程再从管道通过管道输入流拿管道里面的数据 当然进程与进程之间传递数据未必通过这个方式去传递数据, 完全可以在一个进程中设置一个public变量

Linux进程间通信—管道

Linux下的进程通信手段基本上是从UNIX平台上的进程通信手段继承而来的.而对UNIX发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间的通信方面的侧重点有所不同.前者是对UNIX早期的进程间通信手段进行了系统的改进和扩充,形成了"system V IPC",其通信进程主要局限在单个计算机内:后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制.而Linux则把两者的优势都继承了下来 linux进程间通信(