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 = collection.BuildServiceProvider();
        IServiceProviderFactory<IServiceCollection> service = ((IServiceProvider) serviceProvider).GetService<IServiceProviderFactory<IServiceCollection>>();
        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
<IApplicationBuilderFactory, ApplicationBuilderFactory>(); services.AddTransient<IHttpContextFactory, HttpContextFactory>(); services.AddScoped<IMiddlewareFactory, MiddlewareFactory>(); services.AddOptions(); services.AddLogging(); services.AddTransient<IStartupFilter, AutoRequestServicesStartupFilter>(); services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();     ..... foreach (Action<WebHostBuilderContext, IServiceCollection> 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"));
    }

  從上面的代碼流程可以看出

  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<TContext>(IHttpApplication<TContext> 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<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();
        services.AddSingleton<IServer, KestrelServer>();
    });
}

   那麼這個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方法中,一個大概的步驟是這樣的:

  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);

    void 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;
      }

      async Task OnBind(ListenOptions endpoint)
      {
        endpoint.UseHttpServer<TContext>((IList<IConnectionAdapter>) 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<IHostedService> _services;public async Task StartAsync(CancellationToken token)
    {
    
await this.ExecuteAsync((Func<IHostedService, Task>) (service => service.StartAsync(token))); } public async Task StopAsync(CancellationToken token) {
    await this.ExecuteAsync((Func<IHostedService, Task>) (service => service.StopAsync(token))); } private async Task ExecuteAsync(Func<IHostedService, Task> callback) { List<Exception> exceptions = (List<Exception>) null; foreach (IHostedService service in this._services) { try { await callback(service); } catch (Exception ex) { if (exceptions == null) exceptions = new List<Exception>(); exceptions.Add(ex); } } if (exceptions != null) throw new AggregateException((IEnumerable<Exception>) exceptions); } }

  總結

  這兩篇文章從Startup開始到最後的Http管道創建和HttpServer的啓動監聽,涉及到了很多關鍵點,從代碼流程來看,只要抓住幾個關鍵點即可理解整體的一個流程。大家可以帶着以下這些問題去跟着文章走:

  1. Startup有多少種實例化方式?
  2. IStartup在哪裏被實例化的?
  3. IServiceCollection何時實例化的?
  4. IServiceProvider何時實例化的?
  5. Startup的ConfigureService方法何時被執行?
  6. IApplicationBuilder何時實例化的?
  7. Startup的Configure方法何時被執行?
  8. Http監聽管道是何時和如何構建的?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章