55°

ASP.NET Core[源码分析篇] - WebHost

 _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 </span>=<span style="color: #000000;"> collection.BuildServiceProvider();
    IServiceProviderFactory</span>&lt;IServiceCollection&gt; service = ((IServiceProvider) serviceProvider).GetService&lt;IServiceProviderFactory&lt;IServiceCollection&gt;&gt;<span style="color: #000000;">();
    </span><span style="color: #0000ff;">if</span> (service == <span style="color: #0000ff;">null</span><span style="color: #000000;">)
      </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> (IServiceProvider) serviceProvider;
    </span><span style="color: #0000ff;">using</span><span style="color: #000000;"> (serviceProvider)
      </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> service.CreateServiceProvider(service.CreateBuilder(collection));
  }
}</span></pre> 

  这里面有个最重要的方法BuildCommonServices,这个方法实现了委托的真正的执行。

private IServiceCollection BuildCommonServices(
      out AggregateException hostingStartupErrors)
    {
        .....
     ServiceCollection services = new ServiceCollection();
services.AddTransient
<IApplicationBuilderFactory, ApplicationBuilderFactory>(); services.AddTransient<IHttpContextFactory, HttpContextFactory>(); services.AddScoped<IMiddlewareFactory, MiddlewareFactory>(); services.AddOptions(); services.AddLogging(); services.AddTransient<IStartupFilter, AutoRequestServicesStartupFilter>(); services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();

    .....

  </span><span style="color: #0000ff;">foreach</span> (Action&lt;WebHostBuilderContext, IServiceCollection&gt; <strong><span style="color: #ff0000;">servicesDelegate</span> </strong><span style="color: #0000ff;">in</span> <span style="color: #0000ff;">this</span><span style="color: #000000;">.<span style="color: #ff0000;"><strong>_configureServicesDelegates</strong></span>)
    <strong><span style="color: #ff0000;">servicesDelegate</span></strong>(</span><span style="color: #0000ff;">this</span><span style="color: #000000;">._context, (IServiceCollection) <strong><span style="color: #ff0000;">services</span></strong>);
  </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> (IServiceCollection) services;
}</span></pre> 

  从上面的代码我们可以看到,首先创建了一个真正的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); }

</span><span style="color: #0000ff;">private</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"><span style="color: #ff0000;"> EnsureStartup</span>()
{
  </span><span style="color: #0000ff;">if</span> (<span style="color: #0000ff;">this</span>._startup != <span style="color: #0000ff;">null</span><span style="color: #000000;">)
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;">;
  </span><span style="color: #0000ff;">this</span>._startup = <span style="color: #0000ff;">this</span>._hostingServiceProvider.GetService&lt;<span style="color: #ff0000;"><strong>IStartup</strong></span>&gt;<span style="color: #000000;">();
  </span><span style="color: #0000ff;">if</span> (<span style="color: #0000ff;">this</span>._startup == <span style="color: #0000ff;">null</span><span style="color: #000000;">)
    </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span> InvalidOperationException(<span style="color: #0000ff;">string</span>.Format(<span style="color: #800000;">"</span><span style="color: #800000;">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.</span><span style="color: #800000;">"</span>, (<span style="color: #0000ff;">object</span>) <span style="color: #800000;">"</span><span style="color: #800000;">IStartup</span><span style="color: #800000;">"</span>, (<span style="color: #0000ff;">object</span>) <span style="color: #800000;">"</span><span style="color: #800000;">StartupAssemblyKey</span><span style="color: #800000;">"</span><span style="color: #000000;">));
}</span></pre> 

  从上面的代码流程可以看出

  1. 解析Startup类
  2. 执行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<ILogger<WebHost>>();
      this._logger.Starting();
      RequestDelegate application = this.BuildApplication();
      this._applicationLifetime = this._applicationServices.GetRequiredService<Microsoft.AspNetCore.Hosting.IApplicationLifetime>() as ApplicationLifetime;
      this._hostedServiceExecutor = this._applicationServices.GetRequiredService<HostedServiceExecutor>();
      DiagnosticListener requiredService1 = this._applicationServices.GetRequiredService<DiagnosticListener>();
      IHttpContextFactory requiredService2 = this._applicationServices.GetRequiredService<IHttpContextFactory>();
      ILogger<WebHost> logger = this._logger;
      DiagnosticListener diagnosticSource = requiredService1;
      IHttpContextFactory httpContextFactory = requiredService2;
      await this.Server.StartAsync<HostingApplication.Context>((IHttpApplication<HostingApplication.Context>) 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<IApplicationBuilderFactory>().CreateBuilder(this.Server.Features); builder.ApplicationServices = this._applicationServices; IEnumerable<IStartupFilter> service = this._applicationServices.GetService<IEnumerable<IStartupFilter>>(); Action<IApplicationBuilder> next = new Action<IApplicationBuilder>(this._startup.Configure); foreach (IStartupFilter startupFilter in service.Reverse<IStartupFilter>()) 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<IServerAddressesFeature>(); 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</span>&lt;TContext&gt;(IHttpApplication&lt;TContext&gt;<span style="color: #000000;"> application, CancellationToken cancellationToken);

Task StopAsync(CancellationToken cancellationToken);

}

  IServer的实例其实是在开始Program里面的CreateDefaultBuilder中,已经指定了KestrelServer作为默认的Server实例。  

public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder)
{
    hostBuilder.UseLibuv();
</span><span style="color: #0000ff;">return</span> hostBuilder.ConfigureServices(services =&gt;<span style="color: #000000;">
{
    services.AddTransient</span>&lt;IConfigureOptions&lt;KestrelServerOptions&gt;, KestrelServerOptionsSetup&gt;<span style="color: #000000;">();
    services.AddSingleton</span>&lt;IServer, KestrelServer&gt;<span style="color: #000000;">();
});

}

   那么这个Server是做什么用的呢?Server 是一个HTTP服务器,负责HTTP的监听,接收一组 FeatureCollection 类型的原始请求,并将其包装成 HttpContext 以供我们的应用程序完成响应的处理。那它负责监听哪里?从代码可以看到Addresses 是通过在 launchSettings.json 或者是ServerUrlsKey(UseUrls)中来查找的。

  2. BuildApplication

  在上面我们获取了一个Server用来监听请求,那么下一步我们是要构建处理Http请求的管道,IApplicationBuilder 就是用于构建应用程序的请求管道。

  我们一般的管道创建是在 Startup 类的 Configure 方法中对 IApplicationBuilder 进行配置,嗯其实在这里还有一个 IStartupFilter 也可以用来配置 IApplicationBuilder,并且在 Startup 类的Configure 方法之前执行,所有我们看到在BuildApplication方法中,一个大概的步骤是这样的:

  1. 基于IApplicationBuilderFactory创建IApplicationBuilder对象
  2. 基于IStartupFilter的管道构建
  3. 调用IApplicationBuilder对象的Build方法完成完整的管道
public RequestDelegate Build()
    {
      RequestDelegate requestDelegate = (RequestDelegate) (context =>
      {
        context.Response.StatusCode = 404;
        return Task.CompletedTask;
      });
      foreach (Func<RequestDelegate, RequestDelegate> func in this._components.Reverse<Func<RequestDelegate, RequestDelegate>>())
        requestDelegate = func(requestDelegate);
      return requestDelegate;
    }

  3. Server.StartAsync

  在这里,Server的启动是需要一个IHttpApplication类型的参数的,来负责 HttpContext 的创建,我们看一下这个参数

public interface IHttpApplication<TContext>
{
    TContext CreateContext(IFeatureCollection contextFeatures);
Task ProcessRequestAsync(TContext context);

</span><span style="color: #0000ff;">void</span><span style="color: #000000;"> DisposeContext(TContext context, Exception exception);

}

  它的默认实现类是它的默认实现是 HostingApplication 类

public class HostingApplication : IHttpApplication<HostingApplication.Context>
  {
    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<TContext>(
      IHttpApplication<TContext> 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<ListenOptions, Task>(OnBind)).ConfigureAwait(false);
      }
      catch (Exception ex)
      {
        this.Trace.LogCritical((EventId) 0, ex, "Unable to start Kestrel.");
        this.Dispose();
        throw;
      }
  </span><span style="color: #0000ff;">async</span><span style="color: #000000;"> Task <span style="color: #ff0000;"><strong>OnBind</strong></span>(ListenOptions endpoint)
  {
    endpoint.UseHttpServer</span>&lt;TContext&gt;((IList&lt;IConnectionAdapter&gt;) endpoint.ConnectionAdapters, <span style="color: #0000ff;">this</span><span style="color: #000000;">.ServiceContext, application, endpoint.Protocols);
    ConnectionDelegate connectionDelegate </span>=<span style="color: #000000;"> endpoint.Build();
    </span><span style="color: #0000ff;">if</span> (<span style="color: #0000ff;">this</span><span style="color: #000000;">.Options.Limits.MaxConcurrentConnections.HasValue)
      connectionDelegate </span>= <span style="color: #0000ff;">new</span> ConnectionDelegate(<span style="color: #0000ff;">new</span> ConnectionLimitMiddleware(connectionDelegate, <span style="color: #0000ff;">this</span>.Options.Limits.MaxConcurrentConnections.Value, <span style="color: #0000ff;">this</span><span style="color: #000000;">.Trace).OnConnectionAsync);
    ConnectionDispatcher connectionDispatcher </span>= <span style="color: #0000ff;">new</span> ConnectionDispatcher(<span style="color: #0000ff;">this</span><span style="color: #000000;">.ServiceContext, connectionDelegate);
    ITransport transport </span>= <span style="color: #0000ff;">this</span><span style="color: #000000;">._transportFactory.Create((IEndPointInformation) endpoint, (IConnectionDispatcher) connectionDispatcher);
    </span><span style="color: #0000ff;">this</span><span style="color: #000000;">._transports.Add(transport);
    </span><span style="color: #0000ff;">await</span> transport.BindAsync().ConfigureAwait(<span style="color: #0000ff;">false</span><span style="color: #000000;">);
  }
}</span></pre> 

  至此为止,Server已经绑定一个监听端口,注册了HTTP连接事件,剩下的就是开启监听了。  

  4. HostedService

  HostedService 为我们开启了一个后台运行服务,它会在随着程序启动而启动。  

public class HostedServiceExecutor
  {
    private readonly IEnumerable<IHostedService> _services;public async Task StartAsync(CancellationToken token)
    {
    
await this.ExecuteAsync((Func<IHostedService, Task>) (service => service.StartAsync(token))); }
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">async</span><span style="color: #000000;"> Task StopAsync(CancellationToken token)
{<br></span><span style="color: #0000ff;">    await</span> <span style="color: #0000ff;">this</span>.ExecuteAsync((Func&lt;IHostedService, Task&gt;) (service =&gt;<span style="color: #000000;"> service.StopAsync(token)));      </span><span style="color: #000000;">
}

</span><span style="color: #0000ff;">private</span> <span style="color: #0000ff;">async</span> Task ExecuteAsync(Func&lt;IHostedService, Task&gt;<span style="color: #000000;"> callback)
{
  List</span>&lt;Exception&gt; exceptions = (List&lt;Exception&gt;) <span style="color: #0000ff;">null</span><span style="color: #000000;">;
  </span><span style="color: #0000ff;">foreach</span> (IHostedService service <span style="color: #0000ff;">in</span> <span style="color: #0000ff;">this</span><span style="color: #000000;">._services)
  {
    </span><span style="color: #0000ff;">try</span><span style="color: #000000;">
    {
      </span><span style="color: #0000ff;">await</span><span style="color: #000000;"> callback(service);
    }
    </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Exception ex)
    {
      </span><span style="color: #0000ff;">if</span> (exceptions == <span style="color: #0000ff;">null</span><span style="color: #000000;">)
        exceptions </span>= <span style="color: #0000ff;">new</span> List&lt;Exception&gt;<span style="color: #000000;">();
      exceptions.Add(ex);
    }
  }
  </span><span style="color: #0000ff;">if</span> (exceptions != <span style="color: #0000ff;">null</span><span style="color: #000000;">)
    </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span> AggregateException((IEnumerable&lt;Exception&gt;<span style="color: #000000;">) exceptions);
}

}

  总结

  这两篇文章从Startup开始到最后的Http管道创建和HttpServer的启动监听,涉及到了很多关键点,从代码流程来看,只要抓住几个关键点即可理解整体的一个流程。大家可以带着以下这些问题去跟着文章走:

  1. Startup有多少种实例化方式?
  2. IStartup在哪里被实例化的?
  3. IServiceCollection何时实例化的?
  4. IServiceProvider何时实例化的?
  5. Startup的ConfigureService方法何时被执行?
  6. IApplicationBuilder何时实例化的?
  7. Startup的Configure方法何时被执行?
  8. Http监听管道是何时和如何构建的?

本文转载自博客园,原文链接:https://www.cnblogs.com/lex-wu/p/11228612.html

全部评论: 0

    我有话说: