_configureServicesDelegates的承接
在【ASP.NET Core[源码分析篇] - Startup】这篇文章中,我们得知了目前为止(UseStartup),所有的动作都是在_configureServicesDelegates里面添加了注册的委托,那么系统是什么时候执行这些委托完成注册的呢?
真正的注册
通过之前的一系列眼花缭乱的操作,我们得到了所有需要注册的委托_configureServicesDelegates,我们看一下WebHostBuilder.Build如何实现真正的注册。
WebHostBuilder.Build()
public IWebHost Build() { if (this._webHostBuilt) throw new InvalidOperationException(Resources.WebHostBuilder_SingleInstance); this._webHostBuilt = true; AggregateException hostingStartupErrors; IServiceCollection serviceCollection1 = this.BuildCommonServices(out hostingStartupErrors); IServiceCollection serviceCollection2 = serviceCollection1.Clone(); IServiceProvider providerFromFactory = GetProviderFromFactory(serviceCollection1); ..... WebHost webHost = new WebHost(serviceCollection2, providerFromFactory, this._options, this._config, hostingStartupErrors); try { webHost.Initialize(); return (IWebHost) webHost; } catch { webHost.Dispose(); throw; } IServiceProvider GetProviderFromFactory(IServiceCollection collection) { ServiceProvider serviceProvider = collection.BuildServiceProvider(); IServiceProviderFactoryservice = ((IServiceProvider) serviceProvider).GetService >(); if (service == null) return (IServiceProvider) serviceProvider; using (serviceProvider) return service.CreateServiceProvider(service.CreateBuilder(collection)); } }
这里面有个最重要的方法BuildCommonServices,这个方法实现了委托的真正的执行。
private IServiceCollection BuildCommonServices( out AggregateException hostingStartupErrors) { ..... ServiceCollection services = new ServiceCollection();
services.AddTransient(); services.AddTransient (); services.AddScoped (); services.AddOptions(); services.AddLogging(); services.AddTransient (); services.AddTransient , DefaultServiceProviderFactory>(); ..... foreach (Action servicesDelegate in this._configureServicesDelegates) servicesDelegate(this._context, (IServiceCollection) services); return (IServiceCollection) services; }
从上面的代码我们可以看到,首先创建了一个真正的ServiceCollection实例,然后基于这个实例添加了一些额外的重要的注册(ApplicationBuilderFactory,HttpContextFactory,DefaultServiceProviderFactory等),然后把这个ServiceCollection实例作为参数传递到_configureServicesDelegates列表的各个委托中并执行,这样的话所有在Startup需要注册的实例都已经注册在services这个ServiceCollection实例中。
需要注意的是,到此为止程序并没有执行Startup里面的方法。
WebHost
当我们的BuildCommonServices完成后,返回一个ServiceCollection实例,并且基于这个ServiceCollection实例生成了一个ServiceProvider对象,然后做为生成WebHost对象的参数传递到WebHost中。
WebHost webHost = new WebHost(serviceCollection2, providerFromFactory, this._options, this._config, hostingStartupErrors); webHost.Initialize();
WebHost.Initialize()
我们先看一下WebHost的Initialize方法
public void Initialize() { try { this.EnsureApplicationServices(); } catch (Exception ex) { if (this._applicationServices == null) this._applicationServices = (IServiceProvider) this._applicationServiceCollection.BuildServiceProvider(); if (!this._options.CaptureStartupErrors) throw; else this._applicationServicesException = ExceptionDispatchInfo.Capture(ex); } } private void EnsureApplicationServices() { if (this._applicationServices != null) return; this.EnsureStartup(); this._applicationServices = this._startup.ConfigureServices(this._applicationServiceCollection); } private void EnsureStartup() { if (this._startup != null) return; this._startup = this._hostingServiceProvider.GetService<IStartup>(); if (this._startup == null) throw new InvalidOperationException(string.Format("No startup configured. Please specify startup via WebHostBuilder.UseStartup, WebHostBuilder.Configure, injecting {0} or specifying the startup assembly via {1} in the web host configuration.", (object) "IStartup", (object) "StartupAssemblyKey")); }
从上面的代码流程可以看出
- 解析Startup类
- 执行Startup类的ConfigureServices方法注册自定义的服务并返回一个IServiceProvider对象
至此,我们的Startup类中的ConfigureServices已经执行过,并且WebHost已经具有了IServiceProvider对象
WebHost.Run()
当我们调用WebHost的扩展方法Run启动应用的时候,本质上是调用了WebHost的StartAsync方法,这个过程创建了我们应用程序最为重要的用于监听、接收、处理和响应HTTP请求的管道。
public virtual async Task StartAsync(CancellationToken cancellationToken = default (CancellationToken)) { HostingEventSource.Log.HostStart(); this._logger = this._applicationServices.GetRequiredService>(); this._logger.Starting(); RequestDelegate application = this.BuildApplication(); this._applicationLifetime = this._applicationServices.GetRequiredService () as ApplicationLifetime; this._hostedServiceExecutor = this._applicationServices.GetRequiredService (); DiagnosticListener requiredService1 = this._applicationServices.GetRequiredService (); IHttpContextFactory requiredService2 = this._applicationServices.GetRequiredService (); ILogger logger = this._logger; DiagnosticListener diagnosticSource = requiredService1; IHttpContextFactory httpContextFactory = requiredService2; await this.Server.StartAsync ((IHttpApplication ) new HostingApplication(application, (ILogger) logger, diagnosticSource, httpContextFactory), cancellationToken).ConfigureAwait(false); this._applicationLifetime?.NotifyStarted(); await this._hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false); ..... } private RequestDelegate BuildApplication() { this._applicationServicesException?.Throw(); this.EnsureServer(); IApplicationBuilder builder = this._applicationServices.GetRequiredService ().CreateBuilder(this.Server.Features); builder.ApplicationServices = this._applicationServices; IEnumerable service = this._applicationServices.GetService >(); Action next = new Action (this._startup.Configure); foreach (IStartupFilter startupFilter in service.Reverse ()) next = startupFilter.Configure(next); next(builder); return builder.Build(); } private void EnsureServer() { if (this.Server != null) return; this.Server = this._applicationServices.GetRequiredService<IServer>(); IServerAddressesFeature addressesFeature = this.Server.Features?.Get (); ICollection<string> addresses = addressesFeature?.Addresses; if (addresses == null || addresses.IsReadOnly || addresses.Count != 0) return; string str1 = this._config[WebHostDefaults.ServerUrlsKey] ?? this._config[WebHost.DeprecatedServerUrlsKey]; if (string.IsNullOrEmpty(str1)) return; addressesFeature.PreferHostingUrls = WebHostUtilities.ParseBool(this._config, WebHostDefaults.PreferHostingUrlsKey); string str2 = str1; char[] separator = new char[1]{ ';' }; foreach (string str3 in str2.Split(separator, StringSplitOptions.RemoveEmptyEntries)) addresses.Add(str3); }
这块主要是Server的创建,管道的创建和监听Http请求的Server启动,我们将分步进行剖析。
1. EnsureServer
我们先看一下这个Server是什么
public interface IServer : IDisposable { IFeatureCollection Features { get; } Task StartAsync(IHttpApplication application, CancellationToken cancellationToken); Task StopAsync(CancellationToken cancellationToken); }
IServer的实例其实是在开始Program里面的CreateDefaultBuilder中,已经指定了KestrelServer作为默认的Server实例。
public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder) { hostBuilder.UseLibuv(); return hostBuilder.ConfigureServices(services => { services.AddTransient, KestrelServerOptionsSetup>(); services.AddSingleton (); }); }
那么这个Server是做什么用的呢?Server 是一个HTTP服务器,负责HTTP的监听,接收一组 FeatureCollection 类型的原始请求,并将其包装成 HttpContext 以供我们的应用程序完成响应的处理。那它负责监听哪里?从代码可以看到Addresses 是通过在UseUrls里面指定的参数(WebHostDefaults.ServerUrlsKey) 或者是DeprecatedServerUrlsKey(配置文件里面的server.urls)中来查找的。
2. BuildApplication
在上面我们获取了一个Server用来监听请求,那么下一步我们是要构建处理Http请求的管道,IApplicationBuilder 就是用于构建应用程序的请求管道。
我们一般的管道创建是在 Startup 类的 Configure 方法中对 IApplicationBuilder 进行配置,嗯其实在这里还有一个 IStartupFilter 也可以用来配置 IApplicationBuilder,并且在 Startup 类的Configure 方法之前执行,所有我们看到在BuildApplication方法中,一个大概的步骤是这样的:
- 基于IApplicationBuilderFactory创建IApplicationBuilder对象
- 基于IStartupFilter的管道构建
- 调用IApplicationBuilder对象的Build方法完成完整的管道
public RequestDelegate Build() { RequestDelegate requestDelegate = (RequestDelegate) (context => { context.Response.StatusCode = 404; return Task.CompletedTask; }); foreach (Funcfunc in this._components.Reverse >()) requestDelegate = func(requestDelegate); return requestDelegate; }
3. Server.StartAsync
在这里,Server的启动是需要一个IHttpApplication类型的参数的,来负责 HttpContext 的创建,我们看一下这个参数
public interface IHttpApplication{ TContext CreateContext(IFeatureCollection contextFeatures); Task ProcessRequestAsync(TContext context); void DisposeContext(TContext context, Exception exception); }
它的默认实现类是它的默认实现是 HostingApplication 类
public class HostingApplication : IHttpApplication{ private readonly RequestDelegate _application; private readonly IHttpContextFactory _httpContextFactory;public Task ProcessRequestAsync(HostingApplication.Context context) { return this._application(context.HttpContext); }
...... }
我们来看一下Server的Http监听绑定
public async Task StartAsync( IHttpApplication application, CancellationToken cancellationToken) { try { if (!BitConverter.IsLittleEndian) throw new PlatformNotSupportedException(CoreStrings.BigEndianNotSupported); this.ValidateOptions(); if (this._hasStarted) throw new InvalidOperationException(CoreStrings.ServerAlreadyStarted); this._hasStarted = true; this._heartbeat.Start(); await AddressBinder.BindAsync(this._serverAddresses, this.Options, (ILogger) this.Trace, new Func (OnBind)).ConfigureAwait(false); } catch (Exception ex) { this.Trace.LogCritical((EventId) 0, ex, "Unable to start Kestrel."); this.Dispose(); throw; } async Task OnBind(ListenOptions endpoint) { endpoint.UseHttpServer ((IList ) endpoint.ConnectionAdapters, this.ServiceContext, application, endpoint.Protocols); ConnectionDelegate connectionDelegate = endpoint.Build(); if (this.Options.Limits.MaxConcurrentConnections.HasValue) connectionDelegate = new ConnectionDelegate(new ConnectionLimitMiddleware(connectionDelegate, this.Options.Limits.MaxConcurrentConnections.Value, this.Trace).OnConnectionAsync); ConnectionDispatcher connectionDispatcher = new ConnectionDispatcher(this.ServiceContext, connectionDelegate); ITransport transport = this._transportFactory.Create((IEndPointInformation) endpoint, (IConnectionDispatcher) connectionDispatcher); this._transports.Add(transport); await transport.BindAsync().ConfigureAwait(false); } }
至此为止,Server已经绑定一个监听端口,注册了HTTP连接事件,剩下的就是开启监听了。
4. HostedService
HostedService 为我们开启了一个后台运行服务,它会在随着程序启动而启动。
public class HostedServiceExecutor { private readonly IEnumerable_services;public async Task StartAsync(CancellationToken token) {
await this.ExecuteAsync((Func) (service => service.StartAsync(token))); } public async Task StopAsync(CancellationToken token) {
await this.ExecuteAsync((Func) (service => service.StopAsync(token))); } private async Task ExecuteAsync(Func callback) { List exceptions = (List ) null; foreach (IHostedService service in this._services) { try { await callback(service); } catch (Exception ex) { if (exceptions == null) exceptions = new List (); exceptions.Add(ex); } } if (exceptions != null) throw new AggregateException((IEnumerable ) exceptions); } }
总结
这两篇文章从Startup开始到最后的Http管道创建和HttpServer的启动监听,涉及到了很多关键点,从代码流程来看,只要抓住几个关键点即可理解整体的一个流程。大家可以带着以下这些问题去跟着文章走:
- Startup有多少种实例化方式?
- IStartup在哪里被实例化的?
- IServiceCollection何时实例化的?
- IServiceProvider何时实例化的?
- Startup的ConfigureService方法何时被执行?
- IApplicationBuilder何时实例化的?
- Startup的Configure方法何时被执行?
- Http监听管道是何时和如何构建的?