• asp.net core 3.1 源码学习(二)


    IWebHost实现类WebHost

    internal class WebHost : IWebHost, IAsyncDisposable
        {
            private static readonly string DeprecatedServerUrlsKey = "server.urls";
    
            private readonly IServiceCollection _applicationServiceCollection;
            private IStartup _startup;
            private ApplicationLifetime _applicationLifetime;
            private HostedServiceExecutor _hostedServiceExecutor;
    
            private readonly IServiceProvider _hostingServiceProvider;
            private readonly WebHostOptions _options;
            private readonly IConfiguration _config;
            private readonly AggregateException _hostingStartupErrors;
    
            private IServiceProvider _applicationServices;
            private ExceptionDispatchInfo _applicationServicesException;
            private ILogger _logger =  NullLogger.Instance;
    
            private bool _stopped;
            private bool _startedServer;
    
            // Used for testing only
            internal WebHostOptions Options => _options;
    
            private IServer Server { get; set; }
    
            public WebHost(
                IServiceCollection appServices,
                IServiceProvider hostingServiceProvider,
                WebHostOptions options,
                IConfiguration config,
                AggregateException hostingStartupErrors)
            {
                if (appServices == null)
                {
                    throw new ArgumentNullException(nameof(appServices));
                }
    
                if (hostingServiceProvider == null)
                {
                    throw new ArgumentNullException(nameof(hostingServiceProvider));
                }
    
                if (config == null)
                {
                    throw new ArgumentNullException(nameof(config));
                }
    
                _config = config;
                _hostingStartupErrors = hostingStartupErrors;
                _options = options;
                _applicationServiceCollection = appServices;
                _hostingServiceProvider = hostingServiceProvider;
                _applicationServiceCollection.AddSingleton<ApplicationLifetime>();
                // There's no way to to register multiple service types per definition. See https://github.com/aspnet/DependencyInjection/issues/360
                _applicationServiceCollection.AddSingleton(services
                    => services.GetService<ApplicationLifetime>() as IHostApplicationLifetime);
    #pragma warning disable CS0618 // Type or member is obsolete
                _applicationServiceCollection.AddSingleton(services
                    => services.GetService<ApplicationLifetime>() as AspNetCore.Hosting.IApplicationLifetime);
                _applicationServiceCollection.AddSingleton(services
                    => services.GetService<ApplicationLifetime>() as Extensions.Hosting.IApplicationLifetime);
    #pragma warning restore CS0618 // Type or member is obsolete
                _applicationServiceCollection.AddSingleton<HostedServiceExecutor>();
            }
    
            public IServiceProvider Services
            {
                get
                {
                    return _applicationServices;
                }
            }
    
            public IFeatureCollection ServerFeatures
            {
                get
                {
                    EnsureServer();
                    return Server?.Features;
                }
            }
    
            // Called immediately after the constructor so the properties can rely on it.
            public void Initialize()
            {
                try
                {
                    EnsureApplicationServices();
                }
                catch (Exception ex)
                {
                    // EnsureApplicationServices may have failed due to a missing or throwing Startup class.
                    if (_applicationServices == null)
                    {
                        _applicationServices = _applicationServiceCollection.BuildServiceProvider();
                    }
    
                    if (!_options.CaptureStartupErrors)
                    {
                        throw;
                    }
    
                    _applicationServicesException = ExceptionDispatchInfo.Capture(ex);
                }
            }
    
            public void Start()
            {
                StartAsync().GetAwaiter().GetResult();
            }
    
            public virtual async Task StartAsync(CancellationToken cancellationToken = default)
            {
                HostingEventSource.Log.HostStart();
                _logger = _applicationServices.GetRequiredService<ILoggerFactory>().CreateLogger("Microsoft.AspNetCore.Hosting.Diagnostics");
                _logger.Starting();
    
                var application = BuildApplication();
    
                _applicationLifetime = _applicationServices.GetRequiredService<ApplicationLifetime>();
                _hostedServiceExecutor = _applicationServices.GetRequiredService<HostedServiceExecutor>();
    
                // Fire IHostedService.Start
                await _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
    
                var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticListener>();
                var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
                var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory);
                await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);
                _startedServer = true;
    
                // Fire IApplicationLifetime.Started
                _applicationLifetime?.NotifyStarted();
    
    
                _logger.Started();
    
                // Log the fact that we did load hosting startup assemblies.
                if (_logger.IsEnabled(LogLevel.Debug))
                {
                    foreach (var assembly in _options.GetFinalHostingStartupAssemblies())
                    {
                        _logger.LogDebug("Loaded hosting startup assembly {assemblyName}", assembly);
                    }
                }
    
                if (_hostingStartupErrors != null)
                {
                    foreach (var exception in _hostingStartupErrors.InnerExceptions)
                    {
                        _logger.HostingStartupAssemblyError(exception);
                    }
                }
            }
    
            private void EnsureApplicationServices()
            {
                if (_applicationServices == null)
                {
                    EnsureStartup();
                    _applicationServices = _startup.ConfigureServices(_applicationServiceCollection);
                }
            }
    
            private void EnsureStartup()
            {
                if (_startup != null)
                {
                    return;
                }
    
                _startup = _hostingServiceProvider.GetService<IStartup>();
    
                if (_startup == null)
                {
                    throw new InvalidOperationException($"No application configured. Please specify startup via IWebHostBuilder.UseStartup, IWebHostBuilder.Configure, injecting {nameof(IStartup)} or specifying the startup assembly via {nameof(WebHostDefaults.StartupAssemblyKey)} in the web host configuration.");
                }
            }
    
            private RequestDelegate BuildApplication()
            {
                try
                {
                    _applicationServicesException?.Throw();
                    EnsureServer();
    
                    var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
                    var builder = builderFactory.CreateBuilder(Server.Features);
                    builder.ApplicationServices = _applicationServices;
    
                    var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
                    Action<IApplicationBuilder> configure = _startup.Configure;
                    foreach (var filter in startupFilters.Reverse())
                    {
                        configure = filter.Configure(configure);
                    }
    
                    configure(builder);
    
                    return builder.Build();
                }
                catch (Exception ex)
                {
                    if (!_options.SuppressStatusMessages)
                    {
                        // Write errors to standard out so they can be retrieved when not in development mode.
                        Console.WriteLine("Application startup exception: " + ex.ToString());
                    }
                    var logger = _applicationServices.GetRequiredService<ILogger<WebHost>>();
                    logger.ApplicationError(ex);
    
                    if (!_options.CaptureStartupErrors)
                    {
                        throw;
                    }
    
                    EnsureServer();
    
                    // Generate an HTML error page.
                    var hostingEnv = _applicationServices.GetRequiredService<IHostEnvironment>();
                    var showDetailedErrors = hostingEnv.IsDevelopment() || _options.DetailedErrors;
    
                    var model = new ErrorPageModel
                    {
                        RuntimeDisplayName = RuntimeInformation.FrameworkDescription
                    };
                    var systemRuntimeAssembly = typeof(System.ComponentModel.DefaultValueAttribute).GetTypeInfo().Assembly;
                    var assemblyVersion = new AssemblyName(systemRuntimeAssembly.FullName).Version.ToString();
                    var clrVersion = assemblyVersion;
                    model.RuntimeArchitecture = RuntimeInformation.ProcessArchitecture.ToString();
                    var currentAssembly = typeof(ErrorPage).GetTypeInfo().Assembly;
                    model.CurrentAssemblyVesion = currentAssembly
                        .GetCustomAttribute<AssemblyInformationalVersionAttribute>()
                        .InformationalVersion;
                    model.ClrVersion = clrVersion;
                    model.OperatingSystemDescription = RuntimeInformation.OSDescription;
                    model.ShowRuntimeDetails = showDetailedErrors;
    
                    if (showDetailedErrors)
                    {
                        var exceptionDetailProvider = new ExceptionDetailsProvider(
                            hostingEnv.ContentRootFileProvider,
                            sourceCodeLineCount: 6);
    
                        model.ErrorDetails = exceptionDetailProvider.GetDetails(ex);
                    }
                    else
                    {
                        model.ErrorDetails = new ExceptionDetails[0];
                    }
    
                    var errorPage = new ErrorPage(model);
                    return context =>
                    {
                        context.Response.StatusCode = 500;
                        context.Response.Headers[HeaderNames.CacheControl] = "no-cache";
                        return errorPage.ExecuteAsync(context);
                    };
                }
            }
    
            private void EnsureServer()
            {
                if (Server == null)
                {
                    Server = _applicationServices.GetRequiredService<IServer>();
    
                    var serverAddressesFeature = Server.Features?.Get<IServerAddressesFeature>();
                    var addresses = serverAddressesFeature?.Addresses;
                    if (addresses != null && !addresses.IsReadOnly && addresses.Count == 0)
                    {
                        var urls = _config[WebHostDefaults.ServerUrlsKey] ?? _config[DeprecatedServerUrlsKey];
                        if (!string.IsNullOrEmpty(urls))
                        {
                            serverAddressesFeature.PreferHostingUrls = WebHostUtilities.ParseBool(_config, WebHostDefaults.PreferHostingUrlsKey);
    
                            foreach (var value in urls.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
                            {
                                addresses.Add(value);
                            }
                        }
                    }
                }
            }
    
            public async Task StopAsync(CancellationToken cancellationToken = default)
            {
                if (_stopped)
                {
                    return;
                }
                _stopped = true;
    
                _logger.Shutdown();
    
                var timeoutToken = new CancellationTokenSource(Options.ShutdownTimeout).Token;
                if (!cancellationToken.CanBeCanceled)
                {
                    cancellationToken = timeoutToken;
                }
                else
                {
                    cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken).Token;
                }
    
                // Fire IApplicationLifetime.Stopping
                _applicationLifetime?.StopApplication();
    
                if (Server != null && _startedServer)
                {
                    await Server.StopAsync(cancellationToken).ConfigureAwait(false);
                }
    
                // Fire the IHostedService.Stop
                if (_hostedServiceExecutor != null)
                {
                    await _hostedServiceExecutor.StopAsync(cancellationToken).ConfigureAwait(false);
                }
    
                // Fire IApplicationLifetime.Stopped
                _applicationLifetime?.NotifyStopped();
    
                HostingEventSource.Log.HostStop();
            }
    
            public void Dispose()
            {
                DisposeAsync().ConfigureAwait(false).GetAwaiter().GetResult();
            }
    
            public async ValueTask DisposeAsync()
            {
                if (!_stopped)
                {
                    try
                    {
                        await StopAsync().ConfigureAwait(false);
                    }
                    catch (Exception ex)
                    {
                        _logger.ServerShutdownException(ex);
                    }
                }
    
                await DisposeServiceProviderAsync(_applicationServices).ConfigureAwait(false);
                await DisposeServiceProviderAsync(_hostingServiceProvider).ConfigureAwait(false);
            }
    
            private async ValueTask DisposeServiceProviderAsync(IServiceProvider serviceProvider)
            {
                switch (serviceProvider)
                {
                    case IAsyncDisposable asyncDisposable:
                        await asyncDisposable.DisposeAsync();
                        break;
                    case IDisposable disposable:
                        disposable.Dispose();
                        break;
                }
            }
        }

    WebHost初始化时,先验证IStartup接口是否已经注册了

    接下来看下StartAsync方法

    首先调用BuildApplication创建RequestDelegate

    RequestDelegate是一个委托delegate Task RequestDelegate(HttpContext context),用于处理请求

    该方法主要构建处理请求的委托链,看下如何具体实现的

    这里涉及到两个关键接口IApplicationBuilder、IApplicationBuilderFactory

     IApplicationBuilder的实现类

    public class ApplicationBuilder : IApplicationBuilder
        {
            private const string ServerFeaturesKey = "server.Features";
            private const string ApplicationServicesKey = "application.Services";
    
            private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();
    
            public ApplicationBuilder(IServiceProvider serviceProvider)
            {
                Properties = new Dictionary<string, object>(StringComparer.Ordinal);
                ApplicationServices = serviceProvider;
            }
    
            public ApplicationBuilder(IServiceProvider serviceProvider, object server)
                : this(serviceProvider)
            {
                SetProperty(ServerFeaturesKey, server);
            }
    
            private ApplicationBuilder(ApplicationBuilder builder)
            {
                Properties = new CopyOnWriteDictionary<string, object>(builder.Properties, StringComparer.Ordinal);
            }
    
            public IServiceProvider ApplicationServices
            {
                get
                {
                    return GetProperty<IServiceProvider>(ApplicationServicesKey);
                }
                set
                {
                    SetProperty<IServiceProvider>(ApplicationServicesKey, value);
                }
            }
    
            public IFeatureCollection ServerFeatures
            {
                get
                {
                    return GetProperty<IFeatureCollection>(ServerFeaturesKey);
                }
            }
    
            public IDictionary<string, object> Properties { get; }
    
            private T GetProperty<T>(string key)
            {
                object value;
                return Properties.TryGetValue(key, out value) ? (T)value : default(T);
            }
    
            private void SetProperty<T>(string key, T value)
            {
                Properties[key] = value;
            }
    
            public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
            {
                _components.Add(middleware);
                return this;
            }
    
            public IApplicationBuilder New()
            {
                return new ApplicationBuilder(this);
            }
    
            public RequestDelegate Build()
            {
                RequestDelegate app = context =>
                {
                    // If we reach the end of the pipeline, but we have an endpoint, then something unexpected has happened.
                    // This could happen if user code sets an endpoint, but they forgot to add the UseEndpoint middleware.
                    var endpoint = context.GetEndpoint();
                    var endpointRequestDelegate = endpoint?.RequestDelegate;
                    if (endpointRequestDelegate != null)
                    {
                        var message =
                            $"The request reached the end of the pipeline without executing the endpoint: '{endpoint.DisplayName}'. " +
                            $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +
                            $"routing.";
                        throw new InvalidOperationException(message);
                    }
    
                    context.Response.StatusCode = 404;
                    return Task.CompletedTask;
                };
    
                foreach (var component in _components.Reverse())
                {
                    app = component(app);
                }
    
                return app;
            }
        }

    字段_components保存一个委托列表,当调用Use方法时,添加委托到字段中

    最后调用Build方法创建RequestDelegate

    public class ApplicationBuilderFactory : IApplicationBuilderFactory
        {
            private readonly IServiceProvider _serviceProvider;
    
            public ApplicationBuilderFactory(IServiceProvider serviceProvider)
            {
                _serviceProvider = serviceProvider;
            }
    
            public IApplicationBuilder CreateBuilder(IFeatureCollection serverFeatures)
            {
                return new ApplicationBuilder(_serviceProvider, serverFeatures);
            }
        }

    ApplicationBuilderFactory用于创建ApplicationBuilder

    我们再回到BuildApplication中

    应用找到所有实现IStartupFilter的类型,再结合IStartup类型的Configure方法构建委托链,再构建RequestDelegate

    构建好RequestDelegate后,再调用HostedServiceExecutor来执行IHostedService

    internal class HostedServiceExecutor
        {
            private readonly IEnumerable<IHostedService> _services;
            private readonly ILogger<HostedServiceExecutor> _logger;
    
            public HostedServiceExecutor(ILogger<HostedServiceExecutor> logger, IEnumerable<IHostedService> services)
            {
                _logger = logger;
                _services = services;
            }
    
            public Task StartAsync(CancellationToken token)
            {
                return ExecuteAsync(service => service.StartAsync(token));
            }
    
            public Task StopAsync(CancellationToken token)
            {
                return ExecuteAsync(service => service.StopAsync(token), throwOnFirstFailure: false);
            }
    
            private async Task ExecuteAsync(Func<IHostedService, Task> callback, bool throwOnFirstFailure = true)
            {
                List<Exception> exceptions = null;
    
                foreach (var service in _services)
                {
                    try
                    {
                        await callback(service);
                    }
                    catch (Exception ex)
                    {
                        if (throwOnFirstFailure)
                        {
                            throw;
                        }
    
                        if (exceptions == null)
                        {
                            exceptions = new List<Exception>();
                        }
    
                        exceptions.Add(ex);
                    }
                }
    
                // Throw an aggregate exception if there were any exceptions
                if (exceptions != null)
                {
                    throw new AggregateException(exceptions);
                }
            }
        }

    接下来看下IHttpContextFactory怎么创建HttpContext的

     IHttpContextFactory接口的默认实现DefaultHttpContextFactory

    public class DefaultHttpContextFactory : IHttpContextFactory
        {
            private readonly IHttpContextAccessor _httpContextAccessor;
            private readonly FormOptions _formOptions;
            private readonly IServiceScopeFactory _serviceScopeFactory;
    
            // This takes the IServiceProvider because it needs to support an ever expanding
            // set of services that flow down into HttpContext features
            public DefaultHttpContextFactory(IServiceProvider serviceProvider)
            {
                // May be null
                _httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
                _formOptions = serviceProvider.GetRequiredService<IOptions<FormOptions>>().Value;
                _serviceScopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
            }
    
            public HttpContext Create(IFeatureCollection featureCollection)
            {
                if (featureCollection is null)
                {
                    throw new ArgumentNullException(nameof(featureCollection));
                }
    
                var httpContext = new DefaultHttpContext(featureCollection);
                Initialize(httpContext);
                return httpContext;
            }
    
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            internal void Initialize(DefaultHttpContext httpContext, IFeatureCollection featureCollection)
            {
                Debug.Assert(featureCollection != null);
                Debug.Assert(httpContext != null);
    
                httpContext.Initialize(featureCollection);
    
                Initialize(httpContext);
            }
    
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            private DefaultHttpContext Initialize(DefaultHttpContext httpContext)
            {
                if (_httpContextAccessor != null)
                {
                    _httpContextAccessor.HttpContext = httpContext;
                }
    
                httpContext.FormOptions = _formOptions;
                httpContext.ServiceScopeFactory = _serviceScopeFactory;
    
                return httpContext;
            }
    
            public void Dispose(HttpContext httpContext)
            {
                if (_httpContextAccessor != null)
                {
                    _httpContextAccessor.HttpContext = null;
                }
            }
    
            internal void Dispose(DefaultHttpContext httpContext)
            {
                if (_httpContextAccessor != null)
                {
                    _httpContextAccessor.HttpContext = null;
                }
    
                httpContext.Uninitialize();
            }
        }

    再来看下IHttpApplication接口

     实现类HostingApplication:

    internal class HostingApplication : IHttpApplication<HostingApplication.Context>
        {
            private readonly RequestDelegate _application;
            private readonly IHttpContextFactory _httpContextFactory;
            private readonly DefaultHttpContextFactory _defaultHttpContextFactory;
            private HostingApplicationDiagnostics _diagnostics;
    
            public HostingApplication(
                RequestDelegate application,
                ILogger logger,
                DiagnosticListener diagnosticSource,
                IHttpContextFactory httpContextFactory)
            {
                _application = application;
                _diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource);
                if (httpContextFactory is DefaultHttpContextFactory factory)
                {
                    _defaultHttpContextFactory = factory;
                }
                else
                {
                    _httpContextFactory = httpContextFactory;
                }
            }
    
            // Set up the request
            public Context CreateContext(IFeatureCollection contextFeatures)
            {
                Context hostContext;
                if (contextFeatures is IHostContextContainer<Context> container)
                {
                    hostContext = container.HostContext;
                    if (hostContext is null)
                    {
                        hostContext = new Context();
                        container.HostContext = hostContext;
                    }
                }
                else
                {
                    // Server doesn't support pooling, so create a new Context
                    hostContext = new Context();
                }
    
                HttpContext httpContext;
                if (_defaultHttpContextFactory != null)
                {
                    var defaultHttpContext = (DefaultHttpContext)hostContext.HttpContext;
                    if (defaultHttpContext is null)
                    {
                        httpContext = _defaultHttpContextFactory.Create(contextFeatures);
                        hostContext.HttpContext = httpContext;
                    }
                    else
                    {
                        _defaultHttpContextFactory.Initialize(defaultHttpContext, contextFeatures);
                        httpContext = defaultHttpContext;
                    }
                }
                else
                {
                    httpContext = _httpContextFactory.Create(contextFeatures);
                    hostContext.HttpContext = httpContext;
                }
    
                _diagnostics.BeginRequest(httpContext, hostContext);
                return hostContext;
            }
    
            // Execute the request
            public Task ProcessRequestAsync(Context context)
            {
                return _application(context.HttpContext);
            }
    
            // Clean up the request
            public void DisposeContext(Context context, Exception exception)
            {
                var httpContext = context.HttpContext;
                _diagnostics.RequestEnd(httpContext, exception, context);
    
                if (_defaultHttpContextFactory != null)
                {
                    _defaultHttpContextFactory.Dispose((DefaultHttpContext)httpContext);
                }
                else
                {
                    _httpContextFactory.Dispose(httpContext);
                }
    
                _diagnostics.ContextDisposed(context);
    
                // Reset the context as it may be pooled
                context.Reset();
            }
    
    
            internal class Context
            {
                public HttpContext HttpContext { get; set; }
                public IDisposable Scope { get; set; }
                public Activity Activity { get; set; }
    
                public long StartTimestamp { get; set; }
                internal bool HasDiagnosticListener { get; set; }
                public bool EventLogEnabled { get; set; }
    
                public void Reset()
                {
                    // Not resetting HttpContext here as we pool it on the Context
    
                    Scope = null;
                    Activity = null;
    
                    StartTimestamp = 0;
                    HasDiagnosticListener = false;
                    EventLogEnabled = false;
                }
            }
        }

    其中内部类Context对请求进行了封装

    介绍上面的两个类型,主要是IServer的StartAsync方法调用到

    var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticListener>();
                var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
                var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory);
                await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);
                _startedServer = true;
    
                // Fire IApplicationLifetime.Started
                _applicationLifetime?.NotifyStarted();

    这时候进入到了IServer处理请求的环节了

    以上主要介绍了IWebHost的运行原理。

  • 相关阅读:
    打包应用和构建Docker镜像(docker在windows上)
    Docker 在Windows上的安装
    Docker Zero Deployment and Secrets (二)
    Docker Zero Deployment and Secrets (一)
    系统基础信息模块psutil之获取系统性能篇
    IPython的安装方法
    用三种方法设置CentOS7使用代理服务器上网
    CentOS7修改pypi源
    CentOS7升级Python3
    CPU频率的简单测试工具
  • 原文地址:https://www.cnblogs.com/lanpingwang/p/12564189.html
Copyright © 2020-2023  润新知