ASP.NET Core管道由注册的服务器和一系列中间件构成。我们在上一篇中深入剖析了中间件,现在我们来了解一下服务器。服务器是ASP .NET Core管道的第一个节点,它负责完整请求的监听和接收,最终对请求的响应同样也由它完成。[本文已经同步到《ASP.NET Core框架揭秘》之中]

服务器是我们对所有实现了IServer接口的所有类型以及对应对象的统称。如下面的代码片段所示,这个接口具有一个只读属性Features返回描述自身特性集合的FeatureCollection对象,另一个Start方法用于启动服务器。

   1: public interface IServer : IDisposable

   2: {

   3:     IFeatureCollection Features { get; }

   4:     void Start<TContext>(IHttpApplication<TContext> application);    

   5: }

当我们Start方法启动指定的Server的时候,必须指定一个类型为IHttpApplication<TContext>的参数,我们将实现才接口的所有类型及其对应对象统称为HttpApplication。当服务器在接收到抵达的请求之后,它会直接交给这个HttpApplication对象来处理,所以我们需要先来认识一下这个对象。

一、HttpApplication

对于ASP.NET Core管道来说,HttpApplication对会接管服务器接收的请求,后续的请求完全由它来负责。如下图所示,HttpApplication从服务器获得请求之后,会利用注册的中间件注册对请求进行处理,并最终将请求递交给应用程序。HttpApplication针对请求的处理实际上会在一个执行上下文中完成,这个上下文为应用对单一请求的整个处理过程定义了一个边界。单纯描述HTTP请求的HttpContext是这个执行上下文中最为核心的部分,除此之外,我们还可以根据需要将其他相关的信息定义其中,所以IHttpApplication<TContext>接口采用泛型参数的形式来表示定义这个上下文的类型。

HttpApplication不仅仅需要在这个执行上下文中处理服务器转发给它的请求,这个上下文对象的创建和回收释放同样需要由它来完成。如下面的代码片段所示,IHttpApplication<TContext>接口的CreateContext和DisposeContext方法分别体现了针对执行上下文的创建和释放,CreateContext方法的参数contextFeatures表示描述原始上下文的特性集合。在此上下文中针对请求的处理实现在另一个方法ProcessRequestAsync之中。

   1: public interface IHttpApplication<TContext>

   2: {

   3:     TContext CreateContext(IFeatureCollection contextFeatures);

   4:     void     DisposeContext(TContext context, Exception exception);

   5:     Task     ProcessRequestAsync(TContext context);

   6: }

在默认情况下创建的HttpApplication是一个HostingApplication对象。对于HostingApplication来说,它创建的执行上下文的类型是一个具有如下定义的结构Context。对于这个Context对象表示的针对当前请求的执行上下文来说,描述当前HTTP请求的HttpContext是最为核心的部分。除了这个HttpContext属性之外,Context还具有额外两个属性,其中Scope是为追踪诊断而创建的日志上下文范围,该范围将针对同一个请求的多项日志记录进行关联,而另一个属性StartTimestamp表示应用开始处理请求的时间戳。

   1: public class HostingApplication : IHttpApplication<Context>

   2: {

   3:     //省略成员

   4:     public struct Context

   5:     {

   6:         public HttpContext     HttpContext { get; set; }

   7:         public IDisposable     Scope { get; set; }

   8:         public long            StartTimestamp { get; set; }

   9:     }

  10: }

由于HostingApplication针对请求的处理是通过注册的中间件来完成的,而这些中间件最终会利用上面介绍的ApplicationBuilder对象转换成一个类型为RequestDelegate的委托对象,所有中间件对请求的处理通过执行这个委托对象来完成。我们在创建HostingApplication的时候需要提供这么一个RequestDelegate对象。由HostingApplication创建的Context对象包含表示HTTP上下文的HttpContext对象,而后者是通过对应的工厂HttpContextFactory创建的,所以HttpContextFactory在创建时也是必须要提供的。如下面的代码片段所示,HostingApplication类型的构造函数需要将这两个对象作为输入参数,至于另外两个参数(logger和diagnosticSource),它们与日志记录有关。

   1: public class HostingApplication : IHttpApplication<HostingApplication.Context>

   2: {

   3:     private readonly RequestDelegate         _application;

   4:     private readonly DiagnosticSource        _diagnosticSource;

   5:     private readonly IHttpContextFactory     _httpContextFactory;

   6:     private readonly ILogger                 _logger;

   7:  

   8:     public HostingApplication(RequestDelegate application, ILogger logger, DiagnosticSource diagnosticSource, IHttpContextFactory httpContextFactory)

   9:     {

  10:         _application          = application;

  11:         _logger               = logger;

  12:         _diagnosticSource     = diagnosticSource;

  13:         _httpContextFactory   = httpContextFactory;

  14:     }

  15: }

下面给出的代码片段基本体现了HostingApplication创建和释放Context对象,以及在此上下文中处理请求的逻辑。在CreateContext方法中,它直接利用初始化提供的HttpContextFactory创建一个HttpContext并将其作为Context对象的同名属性,至于Context额外两个属性(Scope和StartTimestamp)该作何设置,我们会在本节后续部分对此作专门介绍。实现在ProcessRequestAsync方法中针对请求的处理最终体现在对构造时指定的这个RequestDelegate对象的执行。当DisposeContext方法被执行的时候,Context的Scope属性会率先被释放,在此之后HttpContextFactory的Dispose方法被调用以完成对Context对象自身的回收释放。

   1: public class HostingApplication : IHttpApplication<HostingApplication.Context>

   2: {

   3:     public Context CreateContext(IFeatureCollection contextFeatures)

   4:     {

   5:         //省略其他实现代码

   6:         return new Context

   7:         {

   8:                HttpContext      = _httpContextFactory.Create(contextFeatures),

   9:                Scope            = ...,

  10:                StartTimestamp   = ...

  11:         };

  12:     }

  13:  

  14:     public Task ProcessRequestAsync(Context context)

  15:     {

  16:         Return _application(context.HttpContext);

  17:     }

  18:  

  19:     public void DisposeContext(Context context, Exception exception)

  20:     {        

  21:         //省略其他实现代码

  22:         context.Scope.Dispose();

  23:         _httpContextFactory.Dispose(context.HttpContext);

  24:     }

  25: }

二、KestrelServer

跨平台是ASP.NET Core一个显著的特性,而KestrelServer是目前微软推出了唯一一个能够真正跨平台的服务器。KestrelServer利用一个名为KestrelEngine的网络引擎实现对请求的监听、接收和响应。KetrelServer之所以具有跨平台的特质,源于KestrelEngine是在一个名为libuv的跨平台网络库上开发的。说起libuv,就不得不谈谈libev,后者是Unix系统一个针对事件循环和事件模型的网络库。libev因其具有的高性能成为了继lievent和Event perl module之后一套最受欢迎的网络库。由于Libev不支持Windows,有人在libev之上创建了一个抽象层以屏蔽平台之间的差异,这个抽象层就是libuv。libuv在Windows平台上是采用IOCP的形式实现的,下图揭示了libuv针对Unix和Windows的跨平台实现原理。到目前为止,libuv支持的平台已经不限于Unix和Windows了,包括Linux(2.6)、MacOS和Solaris (121以及之后的版本)在内的平台在libuv支持范围之内。

如下所示的代码片段体现了KestrelServer这个类型的定义。除了实现接口IServer定义的Features属性之外,KestrelServer还具有一个类型为KestrelServerOptions的只读属性Options。这个属性表示对KestrelServer所作的相关设置,我们在调用构造函数时通过输入参数options所代表的IOptions<KestrelServerOptions>对象对这个属性进行初始化。构造函数还具有另两个额外的参数,它们的类型分别是IApplicationLifetime和ILoggerFactory,后者用于创建记录日志的Logger,前者与应用的生命周期管理有关。

   1: public class KestrelServer : IServer

   2: {   

   3:     public IFeatureCollection     Features { get; }

   4:     public KestrelServerOptions   Options { get; }

   5:  

   6:     public KestrelServer(IOptions<KestrelServerOptions> options, IApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory);

   7:     public void Dispose();

   8:     public void Start<TContext>(IHttpApplication<TContext> application);

   9: }

注册的KetrelServer在管道中会以依赖注入的方式被创建,并采用构造器注入的方式提供其构造函数的参数options,由于这个参数类型为IOptions<KestrelServerOptions>,所以我们利用Options模型以配置的方式来指定KestrelServerOptions对象承载的设置。比如我们可以将KestrelServer的相关配置定义在如下一个JSON文件中。

   1: {

   2:   "noDelay"            : false,

   3:   "shutdownTimeout"    : "00:00:10",

   4:   "threadCount"        :  10

   5: }

为了让应用加载这么一个配置文件(文件名假设为“KestrelServerOptions.json”),我们只需要按照如下的方式利用ConfigurationBuilder加载这个配置文件并生成相应的Configuration对象,最后按照Options模型的编程方式完成KestrelServerOptions类型和该对象的映射即可。针对KestrelServerOptions的服务注册也可以定义在启动类型的ConfigureServices方法中。

   1: IConfiguration config = new ConfigurationBuilder()

   2:     .AddJsonFile("KestrelServerOptions.json")

   3:     .Build();

   4:  

   5: new WebHostBuilder()

   6:     .UseKestrel()

   7:     .ConfigureServices(services=>services.Configure<KestrelServerOptions>(config))

   8:      .Configure(app => app.Run(async context => await context.Response.WriteAsync("Hello World")))

   9:     .Build()

  10:     .Run();

我们一般通过调用WebHostBuilder的扩展方法UseKestrel方法来完成对KestrelServer的注册。如下面的代码片段所示,UseKestrel方法具有两个重载,其中一个具有同一个类型为Action<KestrelServerOptions>的参数,我们可以利用这个参数直接完成对KestrelServerOptions的设置。

   1: public static class WebHostBuilderKestrelExtensions

   2: {

   3:     public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder);

   4:     public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder, Action<KestrelServerOptions> options);

   5: }

由于服务器负责请求的监听、接收和响应,所以Server是影响整个Web应用响应能力和吞吐量最大的因素之一,为了更加有效地使用服务器,我们往往针对具体的网络负载状况对其作针对性的设置。对于KestrelServer来说,在构造函数中作为参数指定的KestrelServerOptions对象代表针对它所做的设置。我们针对KestrelServer所做的设置主要体现在KestrelServerOptions类型的如下5个属性上。

   1: public class KestrelServerOptions

   2: {   

   3:     //省略其他成员

   4:     public int          MaxPooledHeaders { get; set; }

   5:     public int          MaxPooledStreams { get; set; }

   6:     public bool         NoDelay { get; set; }

   7:     public TimeSpan     ShutdownTimeout { get; set; }

   8:     public int          ThreadCount { get; set; }

   9: }

三、ServerAddressesFeature

在演示的实例中,我们实际上并不曾为注册的KestrelServer指定一个监听地址,从运行的效果我们不难看出,WebHost在这种情况下会指定“http://localhost:5000”为默认的监听地址。服务器的监听地址自然可以显式指定。在介绍如何通过编程的方式为服务器指定监听地址之前,我们有先来认识一个名为ServerAddressesFeature的特性。

我们知道表示服务器的接口IServer中定义了一个类型为IFeatureCollection 的只读属性Features,它表示用于描述当前服务器的特性集合,ServerAddressesFeature作为一个重要的特性,就包含在这个集合之中。我们所说的ServerAddressesFeature对象是对所有实现了IServerAddressesFeature接口的所有类型及其对应对象的统称,该接口具有一个唯一的只读属性返回服务器的监听地址列表。ASP.NET Core默认使用的ServerAddressesFeature是具有如下定义的同名类型。

   1: public interface IServerAddressesFeature

   2: {

   3:     ICollection<string> Addresses { get; }

   4: }

   5:  

   6: public class ServerAddressesFeature : IServerAddressesFeature

   7: {

   8:     public ICollection<string> Addresses { get; }

   9: }

对于WebHost在通过依赖注入的方式创建的服务器,由它的Features属性表示的特性集合中会默认包含这么一个ServerAddressesFeature对象。如果没有一个合法的监听地址被添加到这个 ServerAddressesFeature对象的地址列表中,WebHost会将显式指定的地址(一个或者多个)添加到该列表中。我们显式指定的监听地址实际上是作为WebHost的配置保存在一个Configuration对象上,配置项对应的Key为“urls”,WebHostDefaults的静态只读属性ServerUrlsKey返回的就是这么一个Key。

   1: new WebHostBuilder()

   2:     .UseSetting(WebHostDefaults.ServerUrlsKey, "http://localhost:3721/")

   3:     .UseMyKestrel()

   4:     .UseStartup<Startup>()

   5:     .Build()

   6:     .Run();

WebHost的配置最初来源于创建它的WebHostBuilder,后者提供了一个UseSettings方法来设置某个配置项的值,所以我们可以采用如上的方式来指定监听地址(“http://localhost:3721/”)。不过,针对监听地址的显式设置,最直接的编程方式还是调用WebHostBuilder的扩展方法UseUrls,如下面的代码片段所示,该方法的实现逻辑与上面完全一致。

   1: public static class WebHostBuilderExtensions

   2: {

   3:     public static IWebHostBuilder UseUrls(this IWebHostBuilder hostBuilder, params string[] urls) 

   4:     =>hostBuilder.UseSetting(WebHostDefaults.ServerUrlsKey, string.Join(ServerUrlsSeparator, urls)) ;    

   5: }

学习ASP.NET Core,怎能不了解请求处理管道[2]: 服务器在管道中的“龙头”地位的更多相关文章

  1. 学习ASP.NET Core Blazor编程系列二——第一个Blazor应用程序(中)

    学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 四.创建一个Blazor应用程序 1. 第一种创 ...

  2. 学习ASP.NET Core, 怎能不了解请求处理管道[3]: 自定义一个服务器感受一下管道是如何监听、接收和响应请求的

    我们在<服务器在管道中的"龙头"地位>中对ASP.NET Core默认提供的具有跨平台能力的KestrelServer进行了介绍,为了让读者朋友们对管道中的服务器具有更 ...

  3. 学习ASP.NET Core, 怎能不了解请求处理管道[6]: 管道是如何随着WebHost的开启被构建出来的?

    注册的服务器和中间件共同构成了ASP.NET Core用于处理请求的管道, 这样一个管道是在我们启动作为应用宿主的WebHost时构建出来的.要深刻了解这个管道是如何被构建出来的,我们就必须对WebH ...

  4. 学习ASP.NET Core, 怎能不了解请求处理管道[5]: 中间件注册可以除了可以使用Startup之外,还可以选择StartupFilter

    中间件的注册除了可以借助Startup对象(DelegateStartup或者ConventionBasedStartup)来完成之外,也可以利用另一个叫做StartupFilter的对象来实现.所谓 ...

  5. 学习ASP.NET Core, 怎能不了解请求处理管道[4]: 应用的入口——Startup

    一个ASP.NET Core应用被启动之后就具有了针对请求的处理能力,而这个能力是由管道赋予的,所以应用的启动同时意味着管道的成功构建.由于管道是由注册的服务器和若干中间件构成的,所以应用启动过程中一 ...

  6. 学习ASP.NET Core,怎能不了解请求处理管道[1]: 中间件究竟是个什么东西?

    ASP.NET Core管道虽然在结构组成上显得非常简单,但是在具体实现上却涉及到太多的对象,所以我们在 "通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流 ...

  7. 学习ASP.NET Core Razor 编程系列九——增加查询功能

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  8. 学习ASP.NET Core Blazor编程系列二——第一个Blazor应用程序(下)

    学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...

  9. 学习ASP.NET Core Blazor编程系列二——第一个Blazor应用程序(完)

    学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...

随机推荐

  1. HTTPS简介

    一.简单总结 1.HTTPS概念总结 HTTPS 就是对HTTP进行了TLS或SSL加密. 应用层的HTTP协议通过传输层的TCP协议来传输,HTTPS 在 HTTP和 TCP中间加了一层TLS/SS ...

  2. WPF 普通属性变化通知

    问题描述:使用ObservableCollection<OrderItem> source 给Datagrid.ItemsSource赋值,在后台更新source集合后,前台Datagri ...

  3. WebLogic的安装和配置以及MyEclipse中配置WebLogic

    WebLogic 中间件: 是基础软件的一大类,属于可复用软件的范畴,顾名思义,中间件属于操作系统软件与应用软件的中间,比如:JDK,框架,weblogic. weblogic与tomcat区别 : ...

  4. arcgis api for js入门开发系列七图层控制(含源代码)

    上一篇实现了demo的地图分屏对比模块,本篇新增图层控制模块,截图如下(源代码见文章底部): 图层控制模块实现的思路如下: 1.在地图配置文件map.config.js里面配置图层目录树节点信息,作为 ...

  5. Register-SPWorkflowService 404

    最近需要做一个SharePoint 2013工作流演示环境. 于是在自己的本子上安装了一个虚拟机. 虚拟机操作系统是Windows Server 2012 R2,计划把AD.SQL Server 20 ...

  6. Android中的多线程断点下载

    首先来看一下多线程下载的原理.多线程下载就是将同一个网络上的原始文件根据线程个数分成均等份,然后每个单独的线程下载对应的一部分,然后再将下载好的文件按照原始文件的顺序"拼接"起来就 ...

  7. Android事件分发机制浅谈(一)

    ---恢复内容开始--- 一.是什么 我们首先要了解什么是事件分发,通俗的讲就是,当一个触摸事件发生的时候,从一个窗口到一个视图,再到一个视图,直至被消费的过程. 二.做什么 在深入学习android ...

  8. Missing Push Notification Entitlement 问题

    最近打包上传是遇到一个问题: 描述: Missing Push Notification Entitlement - Your app includes an API for Apple's Push ...

  9. (资源整理)带你入门Spark

    一.Spark简介: 以下是百度百科对Spark的介绍: Spark 是一种与 Hadoop 相似的开源集群计算环境,但是两者之间还存在一些不同之处,这些有用的不同之处使 Spark 在某些工作负载方 ...

  10. CSS垂直居中总结

    工作中遇到垂直居中问题,特此总结了一下几种方式与大家分享.本文讨论的垂直居中仅支持IE8+ 1.使用绝对定位垂直居中 <div class="container"> & ...