.NET Core开发日志——从ASP.NET Core Module到KestrelServer
ASP.NET Core程序现在变得如同控制台(Console)程序一般,同样通过Main方法启动整个应用。而Main方法要做的事情很简单,创建一个WebHostBuilder类,调用其Build方法生成一个WebHost类,最后启动之。
实现代码一目了然:
public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>(); }
要想探寻其内部究竟做了哪些操作,则需要调查下WebHost类中CreateDefaultBuilder静态方法:
public static IWebHostBuilder CreateDefaultBuilder(string[] args) { var builder = new WebHostBuilder(); if (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.ContentRootKey))) { builder.UseContentRoot(Directory.GetCurrentDirectory()); } if (args != null) { builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build()); } builder.UseKestrel((builderContext, options) => { options.Configure(builderContext.Configuration.GetSection("Kestrel")); }) .ConfigureAppConfiguration((hostingContext, config) => { var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); if (env.IsDevelopment()) { var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); if (appAssembly != null) { config.AddUserSecrets(appAssembly, optional: true); } } config.AddEnvironmentVariables(); if (args != null) { config.AddCommandLine(args); } }) .ConfigureLogging((hostingContext, logging) => { logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); }) .ConfigureServices((hostingContext, services) => { // Fallback services.PostConfigure<HostFilteringOptions>(options => { if (options.AllowedHosts == null || options.AllowedHosts.Count == 0) { // "AllowedHosts": "localhost;127.0.0.1;[::1]" var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); // Fall back to "*" to disable. options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" }); } }); // Change notification services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>( new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration)); services.AddTransient<IStartupFilter, HostFilteringStartupFilter>(); }) .UseIISIntegration() .UseDefaultServiceProvider((context, options) => { options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); }); return builder; }
代码稍微有点多,但这里只关心WebHostBuilder类的创建,以及该builder使用了UseKestrel方法。
UseKestrel方法内部通过IoC的方式注入了KestrelServer类:
public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder) { return hostBuilder.ConfigureServices(services => { // Don't override an already-configured transport services.TryAddSingleton<ITransportFactory, SocketTransportFactory>(); services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>(); services.AddSingleton<IServer, KestrelServer>(); }); }
由此可以知道当一个ASP.NET Core应用程序运行起来时,其内部会有KestrelServer。
那么为什么会需要这个KestrelServer?因为它可以做为一个反向代理服务器,帮助ASP.NET Core实现跨平台的需要。
以传统Windows系统上的IIS为例,如下图所示,ASP.NET Core应用程序中的代码已经不再直接依赖于IIS容器,而是通过KestrelServer这个代理将HTTP请求转换为HttpContext对象,再对此对象进行处理。
图中的ASP.NET Core Module也是由ASP.NET Core的诞生而引入的新的IIS模块。它的主要功能是将Web请求重定向至ASP.NET Core应用程序。并且由于ASP.NET Core应用程序独立运行于IIS工作进程之外的进程,它还负责对进程的管理。
ASP.NET Core Module的源码由C++编写,入口是main文件中的RegisterModule函数。
其函数内部实例化了CProxyModuleFactory工厂类。
pFactory = new CProxyModuleFactory;
而由这个工厂类创建的CProxyModule实例中有一个关键的CProxyModule::OnExecuteRequestHandler方法。它会创建FORWARDING_HANDLER实例,并调用其OnExecuteRequestHandler方法。
__override REQUEST_NOTIFICATION_STATUS CProxyModule::OnExecuteRequestHandler( IHttpContext * pHttpContext, IHttpEventProvider * ) { m_pHandler = new FORWARDING_HANDLER(pHttpContext); if (m_pHandler == NULL) { pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, E_OUTOFMEMORY); return RQ_NOTIFICATION_FINISH_REQUEST; } return m_pHandler->OnExecuteRequestHandler(); }
在此方法里就有那些核心的处理HTTP请求的操作。
// 实例化应用程序管理器 pApplicationManager = APPLICATION_MANAGER::GetInstance(); // 取得应用程序实例 hr = pApplicationManager->GetApplication(m_pW3Context, &m_pApplication); // 取得该应用程序的进程 hr = m_pApplication->GetProcess(m_pW3Context, pAspNetCoreConfig, &pServerProcess); // 创建HTTP请求 hr = CreateWinHttpRequest(pRequest, pProtocol, hConnect, &struEscapedUrl, pAspNetCoreConfig, pServerProcess); // 发送HTTP请求 if (!WinHttpSendRequest(m_hRequest, m_pszHeaders, m_cchHeaders, NULL, 0, cbContentLength, reinterpret_cast<DWORD_PTR>(static_cast<PVOID>(this)))) { hr = HRESULT_FROM_WIN32(GetLastError()); DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, "FORWARDING_HANDLER::OnExecuteRequestHandler, Send request failed"); goto Failure; }
在ASP.NET Core应用程序这端,CreateWebHostBuilder(args).Build().Run();
代码执行之后,会调用其对应的异步方法:
private static async Task RunAsync(this IWebHost host, CancellationToken token, string shutdownMessage) { using (host) { await host.StartAsync(token); var hostingEnvironment = host.Services.GetService<IHostingEnvironment>(); var options = host.Services.GetRequiredService<WebHostOptions>(); if (!options.SuppressStatusMessages) { Console.WriteLine($"Hosting environment: {hostingEnvironment.EnvironmentName}"); Console.WriteLine($"Content root path: {hostingEnvironment.ContentRootPath}"); var serverAddresses = host.ServerFeatures.Get<IServerAddressesFeature>()?.Addresses; if (serverAddresses != null) { foreach (var address in serverAddresses) { Console.WriteLine($"Now listening on: {address}"); } } if (!string.IsNullOrEmpty(shutdownMessage)) { Console.WriteLine(shutdownMessage); } } await host.WaitForTokenShutdownAsync(token); } }
该方法中又调用了WebHost的StartAsync方法:
public virtual async Task StartAsync(CancellationToken cancellationToken = default) { HostingEventSource.Log.HostStart(); _logger = _applicationServices.GetRequiredService<ILogger<WebHost>>(); _logger.Starting(); var application = BuildApplication(); _applicationLifetime = _applicationServices.GetRequiredService<IApplicationLifetime>() as ApplicationLifetime; _hostedServiceExecutor = _applicationServices.GetRequiredService<HostedServiceExecutor>(); 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); // Fire IApplicationLifetime.Started _applicationLifetime?.NotifyStarted(); // Fire IHostedService.Start await _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false); _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); } } }
BuildApplication方法内部从IoC容器取出KestrelServer的实例:
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); } } } } }
最后调用KestrelServer的StartAsync方法:
public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) { try { if (!BitConverter.IsLittleEndian) { throw new PlatformNotSupportedException(CoreStrings.BigEndianNotSupported); } ValidateOptions(); if (_hasStarted) { // The server has already started and/or has not been cleaned up yet throw new InvalidOperationException(CoreStrings.ServerAlreadyStarted); } _hasStarted = true; _heartbeat.Start(); async Task OnBind(ListenOptions endpoint) { // Add the HTTP middleware as the terminal connection middleware endpoint.UseHttpServer(endpoint.ConnectionAdapters, ServiceContext, application, endpoint.Protocols); var connectionDelegate = endpoint.Build(); // Add the connection limit middleware if (Options.Limits.MaxConcurrentConnections.HasValue) { connectionDelegate = new ConnectionLimitMiddleware(connectionDelegate, Options.Limits.MaxConcurrentConnections.Value, Trace).OnConnectionAsync; } var connectionDispatcher = new ConnectionDispatcher(ServiceContext, connectionDelegate); var transport = _transportFactory.Create(endpoint, connectionDispatcher); _transports.Add(transport); await transport.BindAsync().ConfigureAwait(false); } await AddressBinder.BindAsync(_serverAddresses, Options, Trace, OnBind).ConfigureAwait(false); } catch (Exception ex) { Trace.LogCritical(0, ex, "Unable to start Kestrel."); Dispose(); throw; } }
到了这一步,KestrelServer终于可以监听来自ASP.NET Core Module发出的HTTP请求,而ASP.NET Core应用程序也可以开始其自身的任务处理了。
上一篇: 去潮汕旅游有什么好玩的?
下一篇: 抖音开放淘宝店推广链接申请
推荐阅读
-
浅谈从ASP.NET Core2.2到3.0你可能会遇到这些问题
-
浅谈从ASP.NET Core2.2到3.0你可能会遇到这些问题
-
asp.net core 从 3.0 到 3.1
-
asp.net core 从单机到集群
-
ASP.NET Core Web 应用程序开发期间部署到IIS自定义主机域名并附加到进程调试
-
从ASP.NET Core2.2到3.0你可能会遇到这些问题
-
.NET Core开发日志——从ASP.NET Core Module到KestrelServer
-
ASP.NET Core MVC+EF Core从开发到部署
-
.Net Core开发日志——从搭建开发环境开始
-
asp.net core 从 3.0 到 3.1