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 应用程序也可以开始其自身的任务处理了.
来源: https://www.cnblogs.com/kenwoo/p/9309264.html