我们知道ASP.NET Core应用的请求处理管道是由一个IServer对象和IHttpApplication对象构成的。我们可以根据需要注册不同类型的服务器,但在默认情况下,IHttpApplication是一个HostingApplication对象。一个HostingApplication对象由指定的RequestDelegate对象来完成所有的请求处理工作,而后者代表所有中间件按照注册的顺序串联而成的委托链。所有的这一切都被GenericWebHostService整合在一起,在对这个承载Web应用的服务做进一步介绍之前,下面先介绍与它相关的配置选项。[本文节选自《ASP.NET Core 3框架揭秘》第13章, 更多关于ASP.NET Core的文章请点这里]
目录 一、配置选项:GenericWebHostServiceOptions 二、承载服务:GenericWebHostService 三、应用启动流程 四、关闭应用
一、配置选项:GenericWebHostServiceOptions
GenericWebHostService这个承载服务的配置选项类型为GenericWebHostServiceOptions。如下面的代码片段所示,这个内部类型有3个属性,其核心配置选项由WebHostOptions属性承载。GenericWebHostServiceOptions类型的ConfigureApplication属性返回的Action<IApplicationBuilder>对象用来注册中间件,启动过程中针对中间件的注册最终都会转移到这个属性上。
代码语言:javascript复制internal class GenericWebHostServiceOptions
{
public WebHostOptions WebHostOptions { get; set; }
public Action<IApplicationBuilder> ConfigureApplication { get; set; }
public AggregateException HostingStartupExceptions { get; set; }
}
《如何放置你的初始化代码》提出,可以利用一个外部程序集中定义的IHostingStartup实现类型来完成初始化任务,而GenericWebHostServiceOptions类型的HostingStartupExceptions属性返回的AggregateException对象就是对这些初始化任务执行过程中抛出异常的封装。一个WebHostOptions对象承载了与IWebHost相关的配置选项,虽然在基于IHost/IHostBuilder的承载系统中,IWebHost接口作为宿主的作用已经不存在,但是WebHostOptions这个配置选项依然被保留下来。
代码语言:javascript复制public class WebHostOptions
{
public string ApplicationName { get; set; }
public string Environment { get; set; }
public string ContentRootPath { get; set; }
public string WebRoot { get; set; }
public string StartupAssembly { get; set; }
public bool PreventHostingStartup { get; set; }
public IReadOnlyList<string> HostingStartupAssemblies { get; set; }
public IReadOnlyList<string> HostingStartupExcludeAssemblies { get; set; }
public bool CaptureStartupErrors { get; set; }
public bool DetailedErrors { get; set; }
public TimeSpan ShutdownTimeout { get; set; }
public WebHostOptions() => ShutdownTimeout = TimeSpan.FromSeconds(5.0);
public WebHostOptions(IConfiguration configuration);
public WebHostOptions(IConfiguration configuration, string applicationNameFallback);
}
一个WebHostOptions对象可以根据一个IConfiguration对象来创建,当我们调用这个构造函数时,它会根据预定义的配置键从该IConfiguration对象中提取相应的值来初始化对应的属性。
代码语言:javascript复制public static class WebHostDefaults
{
public static readonly string ApplicationKey = "applicationName";
public static readonly string StartupAssemblyKey = "startupAssembly";
public static readonly string DetailedErrorsKey = "detailedErrors";
public static readonly string EnvironmentKey = "environment";
public static readonly string WebRootKey = "webroot";
public static readonly string CaptureStartupErrorsKey = "captureStartupErrors";
public static readonly string ServerUrlsKey = "urls";
public static readonly string ContentRootKey = "contentRoot";
public static readonly string PreferHostingUrlsKey = "preferHostingUrls";
public static readonly string PreventHostingStartupKey = "preventHostingStartup";
public static readonly string ShutdownTimeoutKey = "shutdownTimeoutSeconds";
public static readonly string HostingStartupAssembliesKey= "hostingStartupAssemblies";
public static readonly string HostingStartupExcludeAssembliesKey= "hostingStartupExcludeAssemblies";
}
二、承载服务:GenericWebHostService
从如下所示的代码片段可以看出,GenericWebHostService的构造函数中会注入一系列的依赖服务或者对象,其中包括用来提供配置选项的IOptions<GenericWebHostServiceOptions>对象、作为管道“龙头”的服务器、用来创建ILogger对象的ILoggerFactory对象、用来发送相应诊断事件的DiagnosticListener对象、用来创建HttpContext上下文的IHttpContextFactory对象、用来创建IApplicationBuilder对象的IApplicationBuilderFactory对象、注册的所有IStartupFilter对象、承载当前应用配置的IConfiguration对象和代表当前承载环境的IWebHostEnvironment对象。在GenericWebHostService构造函数中注入的对象或者由它们创建的对象(如由ILoggerFactory对象创建的ILogger对象)最终会存储在对应的属性上。
代码语言:javascript复制internal class GenericWebHostService : IHostedService
{
public GenericWebHostServiceOptions Options { get; }
public IServer Server { get; }
public ILogger Logger { get; }
public ILogger LifetimeLogger { get; }
public DiagnosticListener DiagnosticListener { get; }
public IHttpContextFactory HttpContextFactory { get; }
public IApplicationBuilderFactory ApplicationBuilderFactory { get; }
public IEnumerable<IStartupFilter> StartupFilters { get; }
public IConfiguration Configuration { get; }
public IWebHostEnvironment HostingEnvironment { get; }
public GenericWebHostService(IOptions<GenericWebHostServiceOptions> options,
IServer server, ILoggerFactory loggerFactory,
DiagnosticListener diagnosticListener, IHttpContextFactory httpContextFactory,
IApplicationBuilderFactory applicationBuilderFactory,
IEnumerable<IStartupFilter> startupFilters, IConfiguration configuration,
IWebHostEnvironment hostingEnvironment);
public Task StartAsync(CancellationToken cancellationToken);
public Task StopAsync(CancellationToken cancellationToken);
}
三、应用启动流程
由于ASP.NET Core应用是由GenericWebHostService服务承载的,所以启动应用程序本质上就是启动这个承载服务。承载GenericWebHostService在启动过程中的处理流程基本上体现在如下所示的StartAsync方法中,该方法中刻意省略了一些细枝末节的实现,如输入验证、异常处理、诊断日志事件的发送等。
代码语言:javascript复制internal class GenericWebHostService : IHostedService
{
public Task StartAsync(CancellationToken cancellationToken)
{
//1. 设置监听地址
var serverAddressesFeature = Server.Features?.Get<IServerAddressesFeature>();
var addresses = serverAddressesFeature?.Addresses;
if (addresses != null && !addresses.IsReadOnly && addresses.Count == 0)
{
var urls = Configuration[WebHostDefaults.ServerUrlsKey];
if (!string.IsNullOrEmpty(urls))
{
serverAddressesFeature.PreferHostingUrls = WebHostUtilities.ParseBool(Configuration, WebHostDefaults.PreferHostingUrlsKey);
foreach (var value in urls.Split(new[] { ';' },StringSplitOptions.RemoveEmptyEntries))
{
addresses.Add(value);
}
}
}
//2. 构建中间件管道
var builder = ApplicationBuilderFactory.CreateBuilder(Server.Features);
Action<IApplicationBuilder> configure = Options.ConfigureApplication;
foreach (var filter in StartupFilters.Reverse())
{
configure = filter.Configure(configure);
}
configure(builder);
var handler = builder.Build();
//3. 创建HostingApplication对象
var application = new HostingApplication(handler, Logger, DiagnosticListener, HttpContextFactory);
//4. 启动服务器
return Server.StartAsync(application, cancellationToken);
}
}
我们将实现在GenericWebHostService类型的StartAsync方法中用来启动应用程序的流程划分为如下4个步骤。
- 设置监听地址:服务器的监听地址是通过IServerAddressesFeature接口表示的特性来承载的,所以需要将配置提供的监听地址列表和相关的PreferHostingUrls选项(表示是否优先使用承载系统提供地址)转移到该特性中。
- 构建中间件管道:通过调用IWebHostBuilder对象和注册的Startup类型的Configure方法针对中间件的注册会转换成一个Action<IApplicationBuilder>对象,并复制给配置选项GenericWebHostServiceOptions的ConfigureApplication属性。GenericWebHostService承载服务会利用注册的IApplicationBuilderFactory工厂创建出对应的IApplicationBuilder对象,并将该对象作为参数调用这个Action<IApplicationBuilder>对象就能将注册的中间件转移到IApplicationBuilder对象上。但在此之前,注册IStartupFilter对象的Configure方法会优先被调用,IStartupFilter对象针对前置中间件的注册就体现在这里。代表注册中间件管道的RequestDelegate对象最终通过调用IApplicationBuilder对象的Build方法返回。
- 创建HostingApplication对象:在得到代表中间件管道的RequestDelegate之后,GenericWebHostService对象进一步利用它创建出HostingApplication对象,该对象对于服务器来说就是用来处理由它接收请求的应用程序。
- 启动服务器:将创建出的HostingApplication对象作为参数调用作为服务器的IServer对象的StartAsync方法后,服务器随之被启动。此后,服务器绑定到指定的地址监听抵达的请求,并为接收的请求创建出对应的HttpContext上下文,后续中间件将在这个上下文中完成各自对请求的处理任务。请求处理结束之后,生成的响应最终通过服务器回复给客户端。
四、关闭应用
关闭GenericWebHostService服务之后,只需要按照如下方式关闭服务器即可。除此之外,StopAsync方法还会利用EventSource的形式发送相应的事件,我们在前面针对诊断日志的演示可以体验此功能。
代码语言:javascript复制internal class GenericWebHostService : IHostedService
{
public async Task StopAsync(CancellationToken cancellationToken) => Server.StopAsync(cancellationToken);
}
请求处理管道[1]: 模拟管道实现
请求处理管道[2]: HttpContext本质论
请求处理管道[3]: Pipeline = IServer IHttpApplication<TContext
请求处理管道[4]: 中间件委托链
[请求处理管道5: 应用承载上篇
请求处理管道[6]: 应用承载[下篇]