您的位置:首页 > 编程语言 > ASP

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

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

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

[code] public interface IServer : IDisposable
{
 IFeatureCollection Features{ get;}
void Start<TContext>(IHttpApplication<TContext> application);
}

当我们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之中。

[code] public interface IHttpApplication<TContext>
{
TContext CreateContext(IFeatureCollection contextFeatures);
void DisposeContext(TContext context, Exception exception);
Task ProcessRequestAsync(TContext context);
}

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

[code] public class HostingApplication : IHttpApplication<Context>
{
//省略成员
public struct Context
{
 public HttpContext HttpContext{ get; set;}
 public IDisposable Scope{ get; set;}
 public longStartTimestamp{ get; set;}
}
}

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

[code] public class HostingApplication : IHttpApplication<HostingApplication.Context>
{
private readonly RequestDelegate _application;
private readonly DiagnosticSource_diagnosticSource;
private readonly IHttpContextFactory _httpContextFactory;
private readonly ILogger _logger;
 public HostingApplication(RequestDelegate application, ILogger logger, DiagnosticSource diagnosticSource, IHttpContextFactory httpContextFactory)
{
 _application= application;
 _logger = logger;
 _diagnosticSource = diagnosticSource;
 _httpContextFactory = httpContextFactory;
}
}

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

[code] public class HostingApplication : IHttpApplication<HostingApplication.Context>
{
 public Context CreateContext(IFeatureCollection contextFeatures)
{
 //省略其他实现代码
 return new Context
{
HttpContext= _httpContextFactory.Create(contextFeatures),
Scope= ...,
StartTimestamp = ...
};
}
 public Task ProcessRequestAsync(Context context)
{
 Return _application(context.HttpContext);
}
 public void DisposeContext(Context context, Exception exception)
{
 //省略其他实现代码
 context.Scope.Dispose();
 _httpContextFactory.Dispose(context.HttpContext);
}
}

二、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,前者与应用的生命周期管理有关。

[code] public class KestrelServer : IServer
{ 
 public IFeatureCollection Features{ get;}
 public KestrelServerOptions Options{ get;}
 public KestrelServer(IOptions<KestrelServerOptions> options, IApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory);
 public void Dispose();
 public void Start<TContext>(IHttpApplication<TContext> application);
}

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


[code]{
 "noDelay": false,
 "shutdownTimeout": "00:00:10",
 "threadCount":10
}

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

[code] IConfiguration config = new ConfigurationBuilder()
 .AddJsonFile("KestrelServerOptions.json")
 .Build();
new WebHostBuilder()
 .UseKestrel()
 .ConfigureServices(services=>services.Configure<KestrelServerOptions>(config))
.Configure(app => app.Run(async context => await context.Response.WriteAsync("Hello World")))
 .Build()
 .Run();

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

[code] public static class WebHostBuilderKestrelExtensions
{
 public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder);
 public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder, Action<KestrelServerOptions> options);
}

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

[code] public class KestrelServerOptions
{ 
 //省略其他成员
 public intMaxPooledHeaders{ get; set;}
 public intMaxPooledStreams{ get; set;}
 public bool NoDelay{ get; set;}
 public TimeSpan ShutdownTimeout{ get; set;}
 public intThreadCount{ get; set;}
}

三、ServerAddressesFeature

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

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

[code] public interface IServerAddressesFeature
{
 ICollection<string> Addresses{ get;}
}
public class ServerAddressesFeature : IServerAddressesFeature
{
 public ICollection<string> Addresses{ get;}
}

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


[code]new WebHostBuilder()
 .UseSetting(WebHostDefaults.ServerUrlsKey, "http://localhost:3721/")
 .UseMyKestrel()
 .UseStartup<Startup>()
 .Build()
 .Run();

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

[code] public static class WebHostBuilderExtensions
{
 public static IWebHostBuilder UseUrls(this IWebHostBuilder hostBuilder, params string[] urls) 
 =>hostBuilder.UseSetting(WebHostDefaults.ServerUrlsKey, string.Join(ServerUrlsSeparator, urls)) ;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐