ASP.NET Core[ソース分析編]-WebHost
32412 ワード
_configureServicesDelegatesの引き受け
【ASP.NET Core[ソース分析編]-Startup】この記事では、これまで(UseStartup)、すべての動作が_configureServicesDelegatesに登録依頼が追加されていますが、システムはいつこれらの依頼を実行して登録を完了しますか?
本格的な登録
以前の一連の目まぐるしい操作を通じて、登録が必要なすべての依頼を受けました.configureServicesDelegates、WebHostBuilderを見てみましょう.Buildはどのようにして本当の登録を実現しますか.
WebHostBuilder.Build()
public IWebHost Build()
{
if (this._webHostBuilt)
throw new InvalidOperationException(Resources.WebHostBuilder_SingleInstance);
this._webHostBuilt = true;
AggregateException hostingStartupErrors;
IServiceCollection serviceCollection1 = this.BuildCommonServices(out hostingStartupErrors);
IServiceCollection serviceCollection2 = serviceCollection1.Clone();
IServiceProvider providerFromFactory = GetProviderFromFactory(serviceCollection1);
.....
WebHost webHost = new WebHost(serviceCollection2, providerFromFactory, this._options, this._config, hostingStartupErrors);
try
{
webHost.Initialize();
return (IWebHost) webHost;
}
catch
{
webHost.Dispose();
throw;
}
IServiceProvider GetProviderFromFactory(IServiceCollection collection)
{
ServiceProvider serviceProvider = collection.BuildServiceProvider();
IServiceProviderFactory service = ((IServiceProvider) serviceProvider).GetService>();
if (service == null)
return (IServiceProvider) serviceProvider;
using (serviceProvider)
return service.CreateServiceProvider(service.CreateBuilder(collection));
}
}
そこには最も重要な方法BuildCommonServicesがあり、この方法は依頼の真の実行を実現しています.private IServiceCollection BuildCommonServices(
out AggregateException hostingStartupErrors)
{
.....
ServiceCollection services = new ServiceCollection();
services.AddTransient();
services.AddTransient();
services.AddScoped();
services.AddOptions();
services.AddLogging();
services.AddTransient();
services.AddTransient, DefaultServiceProviderFactory>();
.....
foreach (Action servicesDelegate in this._configureServicesDelegates)
servicesDelegate(this._context, (IServiceCollection) services);
return (IServiceCollection) services;
}
上記のコードから、まず本物のServiceCollectionインスタンスを作成し、このインスタンスに基づいて追加の重要な登録(A p p r i c ationBuilderFactory,HttpContextFactory,DefaultServiceProviderFactoryなど)を追加し、その後、このServiceCollectionインスタンスをパラメータとして_に渡します.configureServicesDelegatesリストの各依頼で実行されます.これにより、Startupに登録する必要があるすべてのインスタンスがサービスというServiceCollectionインスタンスに登録されます.ここまでプログラムはStartup内のメソッドを実行していないことに注意してください.
WebHost
BuildCommonServicesが完了すると、ServiceCollectionインスタンスが返され、このServiceCollectionインスタンスに基づいてServiceProviderオブジェクトが生成され、WebHostオブジェクトを生成するパラメータとしてWebHostに渡されます.
WebHost webHost = new WebHost(serviceCollection2, providerFromFactory, this._options, this._config, hostingStartupErrors);
webHost.Initialize();
WebHost.Initialize()
まずWebHostのInitializeメソッドを見てみましょう
public void Initialize()
{
try
{
this.EnsureApplicationServices();
}
catch (Exception ex)
{
if (this._applicationServices == null)
this._applicationServices = (IServiceProvider) this._applicationServiceCollection.BuildServiceProvider();
if (!this._options.CaptureStartupErrors)
throw;
else
this._applicationServicesException = ExceptionDispatchInfo.Capture(ex);
}
}
private void EnsureApplicationServices()
{
if (this._applicationServices != null)
return;
this.EnsureStartup();
this._applicationServices = this._startup.ConfigureServices(this._applicationServiceCollection);
}
private void EnsureStartup()
{
if (this._startup != null)
return;
this._startup = this._hostingServiceProvider.GetService<IStartup>();
if (this._startup == null)
throw new InvalidOperationException(string.Format("No startup configured. Please specify startup via WebHostBuilder.UseStartup, WebHostBuilder.Configure, injecting {0} or specifying the startup assembly via {1} in the web host configuration.", (object) "IStartup", (object) "StartupAssemblyKey"));
}
上のコードフローから分かるようにこれで、StartupクラスのConfigureServicesが実行され、WebHostにはIServiceProviderオブジェクトがあります.
WebHost.Run()
WebHostの拡張メソッドRunを呼び出してアプリケーションを起動すると、本質的にWebHostのStartAsyncメソッドが呼び出されます.このプロセスは、HTTPリクエストのリスニング、受信、処理、応答に最も重要なパイプラインを作成します.
public virtual async Task StartAsync(CancellationToken cancellationToken = default (CancellationToken))
{
HostingEventSource.Log.HostStart();
this._logger = this._applicationServices.GetRequiredService>();
this._logger.Starting();
RequestDelegate application = this.BuildApplication();
this._applicationLifetime = this._applicationServices.GetRequiredService() as ApplicationLifetime;
this._hostedServiceExecutor = this._applicationServices.GetRequiredService();
DiagnosticListener requiredService1 = this._applicationServices.GetRequiredService();
IHttpContextFactory requiredService2 = this._applicationServices.GetRequiredService();
ILogger logger = this._logger;
DiagnosticListener diagnosticSource = requiredService1;
IHttpContextFactory httpContextFactory = requiredService2;
await this.Server.StartAsync((IHttpApplication) new HostingApplication(application, (ILogger) logger, diagnosticSource, httpContextFactory), cancellationToken).ConfigureAwait(false);
this._applicationLifetime?.NotifyStarted();
await this._hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
.....
}
private RequestDelegate BuildApplication()
{
this._applicationServicesException?.Throw();
this.EnsureServer();
IApplicationBuilder builder = this._applicationServices.GetRequiredService().CreateBuilder(this.Server.Features);
builder.ApplicationServices = this._applicationServices;
IEnumerable service = this._applicationServices.GetService>();
Action next = new Action(this._startup.Configure);
foreach (IStartupFilter startupFilter in service.Reverse())
next = startupFilter.Configure(next);
next(builder);
return builder.Build();
}
private void EnsureServer()
{
if (this.Server != null)
return;
this.Server = this._applicationServices.GetRequiredService<IServer>();
IServerAddressesFeature addressesFeature = this.Server.Features?.Get();
ICollection<string> addresses = addressesFeature?.Addresses;
if (addresses == null || addresses.IsReadOnly || addresses.Count != 0)
return;
string str1 = this._config[WebHostDefaults.ServerUrlsKey] ?? this._config[WebHost.DeprecatedServerUrlsKey];
if (string.IsNullOrEmpty(str1))
return;
addressesFeature.PreferHostingUrls = WebHostUtilities.ParseBool(this._config, WebHostDefaults.PreferHostingUrlsKey);
string str2 = str1;
char[] separator = new char[1]{ ';' };
foreach (string str3 in str2.Split(separator, StringSplitOptions.RemoveEmptyEntries))
addresses.Add(str3);
}
これは主にServerの作成であり、パイプラインの作成とHttpリクエストのServer起動の傍受を行い、段階的に分析します.1. EnsureServer
まずこのサーバーが何なのか見てみましょう
public interface IServer : IDisposable
{
IFeatureCollection Features { get; }
Task StartAsync(IHttpApplication application, CancellationToken cancellationToken);
Task StopAsync(CancellationToken cancellationToken);
}
IServerのインスタンスは、プログラムを開始するCreateDefaultBuilderで、デフォルトのサーバインスタンスとしてKestrelServerが指定されています. public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder)
{
hostBuilder.UseLibuv();
return hostBuilder.ConfigureServices(services =>
{
services.AddTransient, KestrelServerOptionsSetup>();
services.AddSingleton();
});
}
では、このサーバーは何に使いますか?サーバは、HTTPの傍受を担当するHTTPサーバで、FeatureCollectionタイプの元のリクエストのセットを受信し、HttpContextにパッケージして、アプリケーションが応答の処理を完了します.どこを監視してるの?コードからAddressesは、UseUrlsで指定したパラメータ(WebHostDefaults.ServerUrlsKey)またはDeprecatedServerUrlsKey(プロファイル内のserver.urls)で検索されていることがわかります.2. BuildApplication
上記では、要求をリスニングするためのサーバを取得しました.次に、Http要求を処理するパイプを構築します.IApplicationBuilderは、アプリケーションを構築するための要求パイプです.
私たちの一般的なパイプ作成はStartupクラスのコンフィギュレーションメソッドでIApplicationBuilderを構成します.ええ、ここではIApplicationBuilderを構成するためにIStartupFilterも使用できます.また、Startupクラスのコンフィギュレーションメソッドの前に実行します.BuildApplicationメソッドでは、大体のステップは次のようになります.
public RequestDelegate Build()
{
RequestDelegate requestDelegate = (RequestDelegate) (context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
foreach (Func func in this._components.Reverse>())
requestDelegate = func(requestDelegate);
return requestDelegate;
}
3. Server.StartAsync
ここで、サーバの起動には、HttpContextの作成を担当するIHttpApplicationタイプのパラメータが必要です.このパラメータを見てみましょう.
public interface IHttpApplication
{
TContext CreateContext(IFeatureCollection contextFeatures);
Task ProcessRequestAsync(TContext context);
void DisposeContext(TContext context, Exception exception);
}
そのデフォルト実装クラスはそのデフォルト実装がHostingApplicationクラスであるpublic class HostingApplication : IHttpApplication
{
private readonly RequestDelegate _application;
private readonly IHttpContextFactory _httpContextFactory;public Task ProcessRequestAsync(HostingApplication.Context context)
{
return this._application(context.HttpContext);
}
......
}
ServerのHttpリスニングバインディングを見てみましょうpublic async Task StartAsync(
IHttpApplication application,
CancellationToken cancellationToken)
{
try
{
if (!BitConverter.IsLittleEndian)
throw new PlatformNotSupportedException(CoreStrings.BigEndianNotSupported);
this.ValidateOptions();
if (this._hasStarted)
throw new InvalidOperationException(CoreStrings.ServerAlreadyStarted);
this._hasStarted = true;
this._heartbeat.Start();
await AddressBinder.BindAsync(this._serverAddresses, this.Options, (ILogger) this.Trace, new Func(OnBind)).ConfigureAwait(false);
}
catch (Exception ex)
{
this.Trace.LogCritical((EventId) 0, ex, "Unable to start Kestrel.");
this.Dispose();
throw;
}
async Task OnBind(ListenOptions endpoint)
{
endpoint.UseHttpServer((IList) endpoint.ConnectionAdapters, this.ServiceContext, application, endpoint.Protocols);
ConnectionDelegate connectionDelegate = endpoint.Build();
if (this.Options.Limits.MaxConcurrentConnections.HasValue)
connectionDelegate = new ConnectionDelegate(new ConnectionLimitMiddleware(connectionDelegate, this.Options.Limits.MaxConcurrentConnections.Value, this.Trace).OnConnectionAsync);
ConnectionDispatcher connectionDispatcher = new ConnectionDispatcher(this.ServiceContext, connectionDelegate);
ITransport transport = this._transportFactory.Create((IEndPointInformation) endpoint, (IConnectionDispatcher) connectionDispatcher);
this._transports.Add(transport);
await transport.BindAsync().ConfigureAwait(false);
}
}
これまでサーバは傍受ポートを1つバインドし,HTTP接続イベントを登録していたが,残りは傍受を開始した. 4. HostedService
HostedServiceは、プログラムの起動に伴って起動するバックグラウンド実行サービスを開始しました.
public class HostedServiceExecutor
{
private readonly IEnumerable _services;public async Task StartAsync(CancellationToken token)
{
await this.ExecuteAsync((Func) (service => service.StartAsync(token)));
}
public async Task StopAsync(CancellationToken token)
{
await this.ExecuteAsync((Func) (service => service.StopAsync(token)));
}
private async Task ExecuteAsync(Func callback)
{
List exceptions = (List) null;
foreach (IHostedService service in this._services)
{
try
{
await callback(service);
}
catch (Exception ex)
{
if (exceptions == null)
exceptions = new List();
exceptions.Add(ex);
}
}
if (exceptions != null)
throw new AggregateException((IEnumerable) exceptions);
}
}
まとめ
この2つの文章はStartupから最後のHttpパイプ作成とHttpServerの起動リスニングまで、多くのキーに関連しており、コードフローから見ると、いくつかのキーをつかむだけで全体の1つのフローを理解することができます.皆さんは以下の問題を持って文章について行くことができます.