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的运行原理。