ASP.NET Core程式現在變得如同控制台(Console)程式一般,同樣通過Main方法啟動整個應用。而Main方法要做的事情很簡單,創建一個WebHostBuilder類,調用其Build方法生成一個WebHost類,最後啟動之。 實現代碼一目瞭然: 要想探尋其內部究竟做了哪些操作,則需要調 ...
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應用程式也可以開始其自身的任務處理了。