ASP.NET Core 서버 시작까지 흐름 추적
프로그램 엔터리
Program#Main
Program 클래스의 Main 메소드 안은 아래처럼 되어 있다.
이것을 1행씩 따라가 본다.
Program.cs
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build()
.Run();
1행: .CreateDefaultBuilder(args)
WebHostBuilder 인스턴스 만들기
WebHostBuilder 인스턴스를 만든다.
소스 파일: https://github.com/aspnet/AspNetCore/blob/master/src/DefaultBuilder/src/WebHost.cs
var builder = new WebHostBuilder();
환경 변수를 읽도로 한다.
WebHostBuilder 생성자의 주요 부분이다.
ConfigurationBuilder의 확장 메소드를 호출하고, 프리픽스 ASPNETCORE_ 가 붙은 환경 변수를 읽는다.
소스 파일: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/WebHostBuilder.cs
_hostingEnvironment = new HostingEnvironment();
_config = new ConfigurationBuilder()
.AddEnvironmentVariables(prefix: "ASPNETCORE_")
.Build();
_context = new WebHostBuilderContext
{
Configuration = _config
};
ConfigurationBuilder#AddEnvironmentVariables
지정된 프리릭스가 붙은 환경 변수를 읽도록 한다.
소스 파일: https://github.com/aspnet/Extensions/blob/master/src/Configuration/Config.EnvironmentVariables/src/EnvironmentVariablesExtensions.cs
public static IConfigurationBuilder AddEnvironmentVariables(
this IConfigurationBuilder configurationBuilder,
string prefix)
{
configurationBuilder.Add(new EnvironmentVariablesConfigurationSource { Prefix = prefix });
return configurationBuilder;
}
문서 루트를 현재 디렉토리로 한다
if (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.ContentRootKey)))
{
builder.UseContentRoot(Directory.GetCurrentDirectory());
}
명령어 라인 인수를 처리
dotnet run 명령어에서는 아무 것도 넘기지 않으므로 실질적으로 아무 것도 하지 않는다.
src/DefaultBuilder/src/WebHost.cs
if (args != null)
{
builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build());
}
설정 로딩
환경에 따라서 설정 파일을 읽는 delegate를 WebHostBuilder 에 넘기고 있다.
default로는 appsettings.json 파일을 읽는다.
또 appsettings.{환경}.json 파일을 읽는다.
Development 환경 때는 추가 어셈블리를 읽는다.
src/DefaultBuilder/src/WebHost.cs
builder.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);
}
})
logger 설정
logger를 설정한다.
.ConfigureLogging((hostingContext, logging) =>
// (생략)
)
default 서비스 프로바이더를 설정
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
})
default 설정
Kestrel web 서버를 사용한다
.UseKestrel((builderContext, options) =>
{
options.Configure(builderContext.Configuration.GetSection("Kestrel"));
})
소스 파일: https://github.com/aspnet/AspNetCore/blob/master/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs
DI 컨테이너에 추가
.ConfigureServices((hostingContext, services) =>
{
services.PostConfigure<HostFilteringOptions>(/* (생략) */);
services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>(/* (생략) */);
services.AddTransient<IStartupFilter, HostFilteringStartupFilter>();
services.AddRouting();
})
IIS를 사용한다(?)
.UseIIS()
.UseIISIntegration()
소스 파일: https://github.com/aspnet/AspNetCore/blob/master/src/Servers/IIS/IIS/src/WebHostBuilderIISExtensions.cs
2행: .UseStartup()
DI 컨테이너에 StartUp 클래스를 등록하는 delegate를 WebHostBuilder에 추가한다.
실제로는 StartUp 클래스 대신에 ConverntionBasedStartup 클래스를 등록
실제로는 ConverntionBasedStartup 클래스를 사용하도록 한다
소스 파일: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs
return hostBuilder
.ConfigureServices(services =>
{
// (생략)
services.AddSingleton(typeof(IStartup), sp =>
{
var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
});
// (생략)
});
소스 파일: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/Startup/ConventionBasedStartup.cs
StartUp 클래스 호출처 메소드를 결정
ConverntionBasedStartup 클래스를 호출하는 메소드는 StartupLoader에서 결정한다.
소스 파일: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/Internal/StartupLoader.cs
3행: .Build()
WebHost를 빌드한다.
소스 파일: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/WebHostBuilder.cs
공통 서비스 등록
var services = new ServiceCollection();
// (생략)
services.AddSingleton<IHostingEnvironment>(_hostingEnvironment);
services.AddSingleton<Extensions.Hosting.IHostingEnvironment>(_hostingEnvironment);
services.AddSingleton(_context);
// (생략)
var configuration = builder.Build();
services.AddSingleton<IConfiguration>(configuration);
_context.Configuration = configuration;
var listener = new DiagnosticListener("Microsoft.AspNetCore");
services.AddSingleton<DiagnosticListener>(listener);
services.AddSingleton<DiagnosticSource>(listener);
services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
services.AddTransient<IHttpContextFactory, DefaultHttpContextFactory>();
services.AddScoped<IMiddlewareFactory, MiddlewareFactory>();
services.AddOptions();
services.AddLogging();
// (생략)
WebHost 인스턴스를 만든다
WebHost 인스턴스를 만들고, Initialize를 호출하고 있다.
var host = new WebHost(
applicationServices,
hostingServiceProvider,
_options,
_config,
hostingStartupErrors);
(snip)
host.Initialize();
WebHost 초기화
소스 파일: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/Internal/WebHost.cs
StartUp 클래스의 ConfigureServices를 호출
_startup = _hostingServiceProvider.GetService<IStartup>();
_applicationServices = _startup.ConfigureServices(_applicationServiceCollection);
4행: .Run();
WebHost의 StartAsync를 호출
소스 파일: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/WebHostExtensions.cs
await host.StartAsync(token);
소스 파일: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/Internal/WebHost.cs
어플리케이션 빌드
var application = BuildApplication();
서버 만들기
Server = _applicationServices.GetRequiredService<IServer>();
어드레스 추가
var urls = _config[WebHostDefaults.ServerUrlsKey] ?? _config[DeprecatedServerUrlsKey];
// (생략)
foreach (var value in urls.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
addresses.Add(value);
}
ApplicationBuilder 만들기
var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
var builder = builderFactory.CreateBuilder(Server.Features);
builder.ApplicationServices = _applicationServices;
소스 파일: https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/Builder/ApplicationBuilderFactory.cs
return new ApplicationBuilder(_serviceProvider, serverFeatures);
필터 클래스들을 호출
var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
Action<IApplicationBuilder> configure = _startup.Configure;
foreach (var filter in startupFilters.Reverse())
{
configure = filter.Configure(configure);
}
configure(builder);
어플리케이션 빌드
소스 파일: https://github.com/aspnet/AspNetCore/blob/master/src/Http/Http/src/Internal/ApplicationBuilder.cs
_components 에는 use한 delegate가 들어가 있다.
// (생략)
foreach (var component in _components.Reverse())
{
app = component(app);
}
return app;
HostingApplication 인스턴스 만들기
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);
이 글은 2019-08-27에 작성되었습니다.