zoukankan      html  css  js  c++  java
  • ASP.NET Core[源码分析篇]

     _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监听管道是何时和如何构建的?
  • 相关阅读:
    Json基础知识总结
    java连接mysql
    (原)java中对象复制、==、equals
    方便面为什么要是波浪形的 它是怎么做到的?
    软件测试的基本功
    如何在字符串中找到第一个不重复的字符
    无线信道抗衰落技术
    成都市政府公开电话
    学习使用Robot Framework自动化测试框架-web元素定位
    破解受保护的excel中的密码
  • 原文地址:https://www.cnblogs.com/lex-wu/p/11228612.html
Copyright © 2011-2022 走看看