彭小盛

又一个WordPress站点

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

Core,怎能不了解请求处理管道[2]: 服务器在管道中的“龙头”地位 学习ASP.NET-dotNET跨平台曹德信
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 longStartTimestamp { 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的网络引擎实现对请求的监听、接收和响应jk金东旭。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 KestrelServerOptionsOptions { 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 boolNoDelay { 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的扩展方法UseUrlsakclub ,如下面的代码片段所示,该方法的实现逻辑与上面完全一致。
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: }
原文地址:http://www.cnblogs.com/artech/p/asp-net-core-real-pipeline-02.html
.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注