在《管道是如何处理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请求的
四、管道是如何被创建出来的

ASP.NET Core管道深度剖析(4):管道是如何建立起来的?的更多相关文章

  1. ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程

    从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...

  2. ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)

    ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ...

  3. ASP.NET Core 运行原理剖析

    1. ASP.NET Core 运行原理剖析 1.1. 概述 1.2. 文件配置 1.2.1. Starup文件配置 Configure ConfigureServices 1.2.2. appset ...

  4. ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行

    ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行 核心框架 ASP.NET Core APP 创建与运行 总结 之前两篇文章简析.NET Core 以及与 .NET Framew ...

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

    之所以称ASP.NET Core是一个Web开发平台,源于它具有一个极具扩展性的请求处理管道,我们可以通过这个管道的定制来满足各种场景下的HTTP处理需求.ASP. NET Core应用的很多特性,比 ...

  6. ASP.NET Core管道深度剖析(3):管道是如何处理HTTP请求的?

    我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但是就具体的实现来说,由于其中涉及很多对象的交互,我想很少人能够地把它弄清楚.为了让读者 ...

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

    之所以称ASP.NET Core是一个Web开发平台,源于它具有一个极具扩展性的请求处理管道,我们可以通过这个管道的定制来满足各种场景下的HTTP处理需求.ASP. NET Core应用的很多特性,比 ...

  8. ASP.NET Core框架深度学习(二) 管道对象

    4.HttpContext 第一个对象 我们的ASP.NET Core Mini由7个核心对象构建而成.第一个就是大家非常熟悉的HttpContext对象,它可以说是ASP.NET Core应用开发中 ...

  9. ASP.NET Core 2.0 : 八.图说管道

    本文通过一张GIF动图来继续聊一下ASP.NET Core的请求处理管道,从管道的配置.构建以及请求处理流程等方面做一下详细的研究.(ASP.NET Core系列目录) 一.概述 上文说到,请求是经过 ...

随机推荐

  1. 使用HTML5开发Kinect体感游戏

    一.简介 我们要做的是怎样一款游戏? 在前不久成都TGC2016展会上,我们开发了一款<火影忍者手游>的体感游戏,主要模拟手游章节<九尾袭来 >,用户化身四代,与九尾进行对决, ...

  2. MVVM模式解析和在WPF中的实现(五)View和ViewModel的通信

    MVVM模式解析和在WPF中的实现(五) View和ViewModel的通信 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 M ...

  3. C# MVC 5 - 生命周期(应用程序生命周期&请求生命周期)

    本文是根据网上的文章总结的. 1.介绍 本文讨论ASP.Net MVC框架MVC的请求生命周期. MVC有两个生命周期,一为应用程序生命周期,二为请求生命周期. 2.应用程序生命周期 应用程序生命周期 ...

  4. Centos 下 mysql root 密码重置

    重置mysql密码的方法有很多,官网也提供了很方便的快捷操作办法,可参考资料 resetting permissions .本文重置密码的具体步骤如下: 一.停止MySQL(如果处于运行状态) #se ...

  5. webpack的使用

    1.webpack是什么? 打包前端项目的工具(为项目提高逼格的东西). 2.webpack的基本命令 webpack#最基本的启动webpack命令 webpack-w #提供watch方法,实时进 ...

  6. ElasticSearch 5学习(9)——映射和分析(string类型废弃)

    在ElasticSearch中,存入文档的内容类似于传统数据每个字段一样,都会有一个指定的属性,为了能够把日期字段处理成日期,把数字字段处理成数字,把字符串字段处理成字符串值,Elasticsearc ...

  7. iOS微信里打开app,Universal Links

    这两天在弄分享,从第三方应用或者浏览器打开自己app的东西 传统的方式是通过URL Scheme的方式,但是iOS9以后又出了新的更完美的方式Universal Links. 传统的URL Schem ...

  8. MediatorPattern(中介者模式)

    /** * 中介者模式 * @author TMAC-J * 研究了这么多设计模式,觉得无非就是几点: * 1.若两个类有耦合关系,设立一个中间类,处理两个类的关系,把两个类的耦合降低 * 2.面向接 ...

  9. H3 BPM初次安装常见错误详解1-4

    错误1: 首次安装完成无法访问,效果如下. 错误原因:没有配置IIS. 解决方法: 控制面板-程序-打开或关闭Windows功能,选择internet信息服务. 因为安装的时候没有没有iis,所以程序 ...

  10. ionic第二坑——ionic 上拉菜单(ActionSheet)安卓样式坑

    闲话不说,先上图: 这是IOS上的显示效果,代码如下: HTML部分: <body ng-app="starter" ng-controller="actionsh ...