摘要
asp.net core发布至今已经将近6年了,很多人对于这一块还是有些陌生,或者说没接触过;接触过的,对于asp.net core整个启动过程,监听过程,以及请求过程,响应过程也是一知半解,可能有的同学在面试中有被问过整个的启动过程;对此,有个想法就是针对于之前没有接触过core的,后续会持续输出asp.net core方面的基础,包括IOC,中间件,主机,日志,以及服务器,配置,options等方面的入门讲解;本篇博客先粗略的讲解一下,asp.net core整个程序启动过程,以及启动之后都干了什么,我们的请求是如何到达我们的接口的。
WebApplicationBuilder
在asp.net core6,我们默认创建一个项目之后,已经是没有了Main启动方法了,映入眼帘的是去写我们的启动代码,配置服务中间件的代码,在第一行,我们看到直接去构建了一个名为builder的一个对象,这个对象其实就是WebApplicationBuilder的一个对象,在CreateBuilder方法里,直接去new了一个这个类的实例,然后返回给我们。
摘要
asp.net core发布至今已经将近6年了,很多人对于这一块还是有些陌生,或者说没接触过;接触过的,对于asp.net core整个启动过程,监听过程,以及请求过程,响应过程也是一知半解,可能有的同学在面试中有被问过整个的启动过程;对此,有个想法就是针对于之前没有接触过core的,后续会持续输出asp.net core方面的基础,包括IOC,中间件,主机,日志,以及服务器,配置,options等方面的入门讲解;本篇博客先粗略的讲解一下,asp.net core整个程序启动过程,以及启动之后都干了什么,我们的请求是如何到达我们的接口的。
WebApplicationBuilder
在asp.net core6,我们默认创建一个项目之后,已经是没有了Main启动方法了,映入眼帘的是去写我们的启动代码,配置服务中间件的代码,在第一行,我们看到直接去构建了一个名为builder的一个对象,这个对象其实就是WebApplicationBuilder的一个对象,在CreateBuilder方法里,直接去new了一个这个类的实例,然后返回给我们。
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); app.UseSwagger(); app.UseSwaggerUI(); app.UseAuthorization(); app.MapControllers(); app.Run();
在构建了这个类的实例后,这个类的构造函数为我们去构造我们程序运行所必须的一些环境配置,主机配置,以及以来注入的配置,并且有细心的同学可以发现,在3.1以及5的版本中,中间件管理哪里是有自动添加UseRouteing,UseDeveloperExceptionPage和UseEndpoint的方法的,在6中是没有了,其实这个是在构建这个类的实例的时候,默认为我们把这个添加进去了,并且在配置WebHostDefault的时候,已经注入了Routing相关的服务,把我们的需要用的服务器类型,IIS或者Kestrel配置并且注入到容器中去,在源代码中,有个ConfigureApplication的方法在执行了配置WebHostBuilder的默认中间件,这其中就包括了路由和终结点以及异常页方面的中间件配置,并且将WebApplication里面添加的中间件添加到我们构建的applicationbuilder中,这样我们的请求可以走到applicationbuilder中间件去并且在走到我们的WebApplication所添加的中间件,并且在构建WebHostBuilder的实现GenericWebHostBuilder的时候,向我们的容器注入了我们启动需要的HttpContext的工厂实现IHttpContextFactory,以及中间件IMiddlewareFactory,以及我们的ApplicationBuilderFactory的服务,这个服务是用来创建ApplicationBuilder,这个类用来存放我们的中间件并且构建我们整个程序运行的中间件去进行传递,如果有用到UseStartup的话 也会去创建指定的类,然后去调用startup里面的方法,方法参考之前5版本里面的startup;在上述步骤结束后,创建我们WebApplicationBuilder里面的Host对象和WebHost的对象的实例;这其中涉及到了几个重要的类和方法,ConfigurationManager是我们程序的配置文件相关的类,BootstrapHostBuilder用来配置默认的ConfigureWebHostDefaults,并且在初始化完成之后会将HostBuilderContext传递到我们ConfigureHostBuilder这个类去,这个类是我们builder.host的类型,ConfigureWebHostBuilder用来配置web主机启动的时候的一些配置
var configuration = new ConfigurationManager(); configuration.AddEnvironmentVariables(prefix: "ASPNETCORE_"); _hostApplicationBuilder = new HostApplicationBuilder(new HostApplicationBuilderSettings { Args = options.Args, ApplicationName = options.ApplicationName, EnvironmentName = options.EnvironmentName, ContentRootPath = options.ContentRootPath, Configuration = configuration, }); // Set WebRootPath if necessary if (options.WebRootPath is not null) { Configuration.AddInMemoryCollection(new[] { new KeyValuePair<string, string?>(WebHostDefaults.WebRootKey, options.WebRootPath), }); } // Run methods to configure web host defaults early to populate services var bootstrapHostBuilder = new BootstrapHostBuilder(_hostApplicationBuilder); // This is for testing purposes configureDefaults?.Invoke(bootstrapHostBuilder); bootstrapHostBuilder.ConfigureWebHostDefaults(webHostBuilder => { // Runs inline. webHostBuilder.Configure(ConfigureApplication); webHostBuilder.UseSetting(WebHostDefaults.ApplicationKey, _hostApplicationBuilder.Environment.ApplicationName ?? ""); webHostBuilder.UseSetting(WebHostDefaults.PreventHostingStartupKey, Configuration[WebHostDefaults.PreventHostingStartupKey]); webHostBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, Configuration[WebHostDefaults.HostingStartupAssembliesKey]); webHostBuilder.UseSetting(WebHostDefaults.HostingStartupExcludeAssembliesKey, Configuration[WebHostDefaults.HostingStartupExcludeAssembliesKey]); }, options => { // We've already applied "ASPNETCORE_" environment variables to hosting config options.SuppressEnvironmentConfiguration = true; }); // This applies the config from ConfigureWebHostDefaults // Grab the GenericWebHostService ServiceDescriptor so we can append it after any user-added IHostedServices during Build(); _genericWebHostServiceDescriptor = bootstrapHostBuilder.RunDefaultCallbacks(); // Grab the WebHostBuilderContext from the property bag to use in the ConfigureWebHostBuilder. Then // grab the IWebHostEnvironment from the webHostContext. This also matches the instance in the IServiceCollection. var webHostContext = (WebHostBuilderContext)bootstrapHostBuilder.Properties[typeof(WebHostBuilderContext)]; Environment = webHostContext.HostingEnvironment; Host = new ConfigureHostBuilder(bootstrapHostBuilder.Context, Configuration, Services); WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services);
Debug.Assert(_builtApplication is not null); // UseRouting called before WebApplication such as in a StartupFilter // lets remove the property and reset it at the end so we don't mess with the routes in the filter if (app.Properties.TryGetValue(EndpointRouteBuilderKey, out var priorRouteBuilder)) { app.Properties.Remove(EndpointRouteBuilderKey); } if (context.HostingEnvironment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } // Wrap the entire destination pipeline in UseRouting() and UseEndpoints(), essentially: // destination.UseRouting() // destination.Run(source) // destination.UseEndpoints() // Set the route builder so that UseRouting will use the WebApplication as the IEndpointRouteBuilder for route matching app.Properties.Add(WebApplication.GlobalEndpointRouteBuilderKey, _builtApplication); // Only call UseRouting() if there are endpoints configured and UseRouting() wasn't called on the global route builder already if (_builtApplication.DataSources.Count > 0) { // If this is set, someone called UseRouting() when a global route builder was already set if (!_builtApplication.Properties.TryGetValue(EndpointRouteBuilderKey, out var localRouteBuilder)) { app.UseRouting(); } else { // UseEndpoints will be looking for the RouteBuilder so make sure it's set app.Properties[EndpointRouteBuilderKey] = localRouteBuilder; } } // Wire the source pipeline to run in the destination pipeline app.Use(next => { _builtApplication.Run(next); return _builtApplication.BuildRequestDelegate(); }); if (_builtApplication.DataSources.Count > 0) { // We don't know if user code called UseEndpoints(), so we will call it just in case, UseEndpoints() will ignore duplicate DataSources app.UseEndpoints(_ => { }); } // Copy the properties to the destination app builder foreach (var item in _builtApplication.Properties) { app.Properties[item.Key] = item.Value; } // Remove the route builder to clean up the properties, we're done adding routes to the pipeline app.Properties.Remove(WebApplication.GlobalEndpointRouteBuilderKey); // reset route builder if it existed, this is needed for StartupFilters if (priorRouteBuilder is not null) { app.Properties[EndpointRouteBuilderKey] = priorRouteBuilder; }
WebApplication
上面我们讲了WebApplicationBuilder类,在这个类里面我们开始构建了Hostbuilder的类的实例,然后我们在我们的代码中调用了这个类的Builder的方法,这个方法是将Hostbuilder里面build的方法调用之后返回的IHost参数传递到WebApplication类中去,通过构造函数传入,同时这个类 IHost, IApplicationBuilder, IEndpointRouteBuilder分别实现了这三个接口,第一个IHost是我们程序运行时所以来的主机通过启动主机我们去启动我们的服务,不管是IIS还是Kestrel,第二个的话就是我们的管道中间件配置接口,所有的我们使用的中间件都最终调用这个接口下面的Use方法添加到中间件集合中去,第三个接口则是指定了我们所有路由终结点的Endpoint的数据源以及,依赖注入的服务提供者。在这个类里面,我们可以获取到我们的服务提供者以及日志Logger相关,配置,等相关接口的实例,这些在我们CreateBuilder的时候都以及配置和注入好了,在这里我们就可以直接配置我们所需要的各种中间件。同时刚才也说了,这个类实现了IApplicationBuilder,所以我们也可以直接调用Use方法添加我们的中间件,并且也有许多拓展的方法供我们去向IApplicationBuilder添加中间件。
在所有的配置都就绪好之后,我们便可以去启动我们的主机,从而去启动我们的web主机,可以看到,我们最后的代码是app.run,这个方法就是在调用我们WebApplication构造函数传入的IHost里面的StartAsync方法,接下来我们看这个类里面的实现。
MapControllers
这里需要着重讲一下这个方法,我们都知道,我们所有的请求都会走到useendpoint的中间件去,那在这个中间件之前我们是需要把我们的所有的路由信息添加到一个EndpointSource的集合中去的,这里面包含了你的方法名称,元数据以及RequestDelegate的信息,包含了你的方法请求的路由等信息,所以在MapController方法,其实就是在构建我们所有的路由请求的一个RequestDelegate,然后在每次请求的时候,在EndpointMiddleWare中间件去执行这个RequestDelegate,从而走到我们的接口中去。简而言之,这个方法就是将我们的所有路由信息添加到一个EndpointDataSource的抽象类的实现类中去,默认是ControllerActionEndpointDataSource这个类,在这个类中有一个基类ActionEndpointDataSourceBase,ControllerActionEndpointDataSource初始化的时候会订阅所有的Endpoint的集合的变化,每变化一次会向EndpointSource集合添加Endpoint,从而在请求的时候可以找到这个终结点去调用,
public static ControllerActionEndpointConventionBuilder MapControllers(this IEndpointRouteBuilder endpoints) { if (endpoints == null) { throw new ArgumentNullException(nameof(endpoints)); } EnsureControllerServices(endpoints); return GetOrCreateDataSource(endpoints).DefaultBuilder; }
private static ControllerActionEndpointDataSource GetOrCreateDataSource(IEndpointRouteBuilder endpoints) { var dataSource = endpoints.DataSources.OfType<ControllerActionEndpointDataSource>().FirstOrDefault(); if (dataSource == null) { var orderProvider = endpoints.ServiceProvider.GetRequiredService<OrderedEndpointsSequenceProviderCache>(); var factory = endpoints.ServiceProvider.GetRequiredService<ControllerActionEndpointDataSourceFactory>(); dataSource = factory.Create(orderProvider.GetOrCreateOrderedEndpointsSequenceProvider(endpoints)); endpoints.DataSources.Add(dataSource); } return dataSource; }
IHost
在app.run方法之后,最后会调用我们构造函数中的Host的StartAsync方法去,可以看一下这里的调用源码,在我们run的时候,调用了HostingAbstractionsHostExtensions里面的run方法,然后这个HostingAbstractionsHostExtensions的run方法又调用了WebApplication里面的Runasync方法,WebApplication的RunAsync方法又调用了HostingAbstractionsHostExtensions的RunAsync方法,这个HostingAbstractionsHostExtensions的RunAsync方法又调用了WebApplication的StartAsync方法,然后去调用了我们的Host的StartAsync方法,哈哈,是不是很绕,看到这段调用代码,我甚至觉得太扯了。我们都知道core的运行其实就是HostedService去启动我们的Web服务的,所以在这个start方法里面,他从ServiceProvider去获取了所有的实现了HostedService接口的实例,然后循环去调用StartAsync方法,这里引入我们的泛型主机的一个实现,GenericWebHostService这个类,同样实现了HostdService的接口,然后我们在Host的startasync方法调用之后会走到这个类的StartAsync方法中去,这个类的构造函数中已经传入了我们所需要的IServer的类型,这个就是我们的运行所以来的web服务器,是iis或者Kestrel,然后在这个GenericWebHostService的StartAsync方法中去调用IServer的StartAsync方法启动我们的服务监听。并且在监听之前,会把我们的所有的中间件去build一个RequestDelegate,然后传递到IHttpApplication这个泛型接口中去,这个接口其实就是我们所有的请求走中间件的地方,并且也是根据我们的Request去创建HttpContext的地方,从而去构建Request和Response实例的地方,
KestrelServerImpl
其实在这个类之上还有一个KestrelServer类,两个都实现了IServer接口,在上面的Host调用IServer的StartAsync方法之后,调用了KestrelServer的StartAsync方法,然后在调用到了KestrelServerImpl的StartAsync方法,这个类里面的StartAsync方法,在开始的时候就去开始我们程序的心跳。然后调用了一个BindAsync的方法,在此上面我们将我们需要监听的地址,以及BindAsync之后的回调传入到AddressBindContext这个类中;
StartAsync
public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) where TContext : notnull { try { ValidateOptions(); if (_hasStarted) { // The server has already started and/or has not been cleaned up yet throw new InvalidOperationException(CoreStrings.ServerAlreadyStarted); } _hasStarted = true; ServiceContext.Heartbeat?.Start(); async Task OnBind(ListenOptions options, CancellationToken onBindCancellationToken) { var hasHttp1 = options.Protocols.HasFlag(HttpProtocols.Http1); var hasHttp2 = options.Protocols.HasFlag(HttpProtocols.Http2); var hasHttp3 = options.Protocols.HasFlag(HttpProtocols.Http3); var hasTls = options.IsTls; // Filter out invalid combinations. if (!hasTls) { // Http/1 without TLS, no-op HTTP/2 and 3. if (hasHttp1) { hasHttp2 = false; hasHttp3 = false; } // Http/3 requires TLS. Note we only let it fall back to HTTP/1, not HTTP/2 else if (hasHttp3) { throw new InvalidOperationException("HTTP/3 requires HTTPS."); } } // Quic isn't registered if it's not supported, throw if we can't fall back to 1 or 2 if (hasHttp3 && _multiplexedTransportFactory is null && !(hasHttp1 || hasHttp2)) { throw new InvalidOperationException("This platform doesn't support QUIC or HTTP/3."); } // Disable adding alt-svc header if endpoint has configured not to or there is no // multiplexed transport factory, which happens if QUIC isn't supported. var addAltSvcHeader = !options.DisableAltSvcHeader && _multiplexedTransportFactory != null; // Add the HTTP middleware as the terminal connection middleware if (hasHttp1 || hasHttp2 || options.Protocols == HttpProtocols.None) // TODO a test fails because it doesn't throw an exception in the right place // when there is no HttpProtocols in KestrelServer, can we remove/change the test? { if (_transportFactory is null) { throw new InvalidOperationException($"Cannot start HTTP/1.x or HTTP/2 server if no {nameof(IConnectionListenerFactory)} is registered."); } options.UseHttpServer(ServiceContext, application, options.Protocols, addAltSvcHeader); var connectionDelegate = options.Build(); // Add the connection limit middleware connectionDelegate = EnforceConnectionLimit(connectionDelegate, Options.Limits.MaxConcurrentConnections, Trace); options.EndPoint = await _transportManager.BindAsync(options.EndPoint, connectionDelegate, options.EndpointConfig, onBindCancellationToken).ConfigureAwait(false); } if (hasHttp3 && _multiplexedTransportFactory is not null) { options.UseHttp3Server(ServiceContext, application, options.Protocols, addAltSvcHeader); var multiplexedConnectionDelegate = ((IMultiplexedConnectionBuilder)options).Build(); // Add the connection limit middleware multiplexedConnectionDelegate = EnforceConnectionLimit(multiplexedConnectionDelegate, Options.Limits.MaxConcurrentConnections, Trace); options.EndPoint = await _transportManager.BindAsync(options.EndPoint, multiplexedConnectionDelegate, options, onBindCancellationToken).ConfigureAwait(false); } } AddressBindContext = new AddressBindContext(_serverAddresses, Options, Trace, OnBind); await BindAsync(cancellationToken).ConfigureAwait(false); } catch { // Don't log the error https://github.com/dotnet/aspnetcore/issues/29801 Dispose(); throw; } // Register the options with the event source so it can be logged (if necessary) KestrelEventSource.Log.AddServerOptions(Options); }
AddressBindContext
BindAsync
在BindAsync方法我们看到我们调用了AddressBinder.BindAsync的方法,
private async Task BindAsync(CancellationToken cancellationToken) { await _bindSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { if (_stopping == 1) { throw new InvalidOperationException("Kestrel has already been stopped."); } IChangeToken? reloadToken = null; _serverAddresses.InternalCollection.PreventPublicMutation(); if (Options.ConfigurationLoader?.ReloadOnChange == true && (!_serverAddresses.PreferHostingUrls || _serverAddresses.InternalCollection.Count == 0)) { reloadToken = Options.ConfigurationLoader.Configuration.GetReloadToken(); } Options.ConfigurationLoader?.Load(); await AddressBinder.BindAsync(Options.ListenOptions, AddressBindContext!, cancellationToken).ConfigureAwait(false); _configChangedRegistration = reloadToken?.RegisterChangeCallback(TriggerRebind, this); } finally { _bindSemaphore.Release(); } }
AddressBinder.Bindasync
在这个方法我们调用了IStrategy的BindAsync方法,这个接口有多个实现,但是不管有多少个最后都会调用了我们AddressBindContext方法中的CreateBinding委托,可以结合上面的方法看我们的CreateBinding委托实际上就是我们StartAsync中的OnBind方法。
在OnBind方法中,我们判断我们的Http版本是1,2还是3,不管是哪个版本,这里的UseHttpServer和UseHttp3Server都是构建了一个在有监听请求之后的一个ConnectionDelegate,用来监听到请求之后,去进行处理我们的Request。这里我们需要着重看一下_transportManager.BindAsync方法,如果我们没有指定使用其他方式去进行监听,例如QUIC,默认都是使用Socket进行监听的,所以IConnectionListenerFactory接口其中的一个实现就是SocketTransportFactory,默认的就走到了SocketTransportFactory.BindAsync方法中去,在这个方法,我们启动了一个Socket的监听,然后调用了Bind方法去启动这个监听,这样我们便启动了我们服务器,然后接下来就是一直等待连接请求,在TransportManager.StartAcceptLoop方法中,我们最主要用的用来处理连接的一个类叫ConnectionDispatcher的类,这个类里面我们调用了StartAcceptingConnections的方法。
var strategy = CreateStrategy( listenOptions.ToArray(), context.Addresses.ToArray(), context.ServerAddressesFeature.PreferHostingUrls); // reset options. The actual used options and addresses will be populated // by the address binding feature context.ServerOptions.OptionsInUse.Clear(); context.Addresses.Clear(); await strategy.BindAsync(context, cancellationToken).ConfigureAwait(false);
OnConnectionAsync
public static IConnectionBuilder UseHttpServer<TContext>(this IConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication<TContext> application, HttpProtocols protocols, bool addAltSvcHeader) where TContext : notnull { var middleware = new HttpConnectionMiddleware<TContext>(serviceContext, application, protocols, addAltSvcHeader); return builder.Use(next => { return middleware.OnConnectionAsync; }); } public static IMultiplexedConnectionBuilder UseHttp3Server<TContext>(this IMultiplexedConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication<TContext> application, HttpProtocols protocols, bool addAltSvcHeader) where TContext : notnull { var middleware = new HttpMultiplexedConnectionMiddleware<TContext>(serviceContext, application, protocols, addAltSvcHeader); return builder.Use(next => { return middleware.OnConnectionAsync; }); }
public Task OnConnectionAsync(ConnectionContext connectionContext) { var memoryPoolFeature = connectionContext.Features.Get<IMemoryPoolFeature>(); var protocols = connectionContext.Features.Get<HttpProtocolsFeature>()?.HttpProtocols ?? _endpointDefaultProtocols; var localEndPoint = connectionContext.LocalEndPoint as IPEndPoint; var altSvcHeader = _addAltSvcHeader && localEndPoint != null ? HttpUtilities.GetEndpointAltSvc(localEndPoint, protocols) : null; var httpConnectionContext = new HttpConnectionContext( connectionContext.ConnectionId, protocols, altSvcHeader, connectionContext, _serviceContext, connectionContext.Features, memoryPoolFeature?.MemoryPool ?? System.Buffers.MemoryPool<byte>.Shared, localEndPoint, connectionContext.RemoteEndPoint as IPEndPoint); httpConnectionContext.Transport = connectionContext.Transport; var connection = new HttpConnection(httpConnectionContext); return connection.ProcessRequestsAsync(_application); }
TransportManager
public async Task<EndPoint> BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig? endpointConfig, CancellationToken cancellationToken) { if (_transportFactory is null) { throw new InvalidOperationException($"Cannot bind with {nameof(ConnectionDelegate)} no {nameof(IConnectionListenerFactory)} is registered."); } var transport = await _transportFactory.BindAsync(endPoint, cancellationToken).ConfigureAwait(false); StartAcceptLoop(new GenericConnectionListener(transport), c => connectionDelegate(c), endpointConfig); return transport.EndPoint; } public async Task<EndPoint> BindAsync(EndPoint endPoint, MultiplexedConnectionDelegate multiplexedConnectionDelegate, ListenOptions listenOptions, CancellationToken cancellationToken) { if (_multiplexedTransportFactory is null) { throw new InvalidOperationException($"Cannot bind with {nameof(MultiplexedConnectionDelegate)} no {nameof(IMultiplexedConnectionListenerFactory)} is registered."); } var features = new FeatureCollection(); // This should always be set in production, but it's not set for InMemory tests. // The transport will check if the feature is missing. if (listenOptions.HttpsOptions != null) { features.Set(HttpsConnectionMiddleware.CreateHttp3Options(listenOptions.HttpsOptions)); } var transport = await _multiplexedTransportFactory.BindAsync(endPoint, features, cancellationToken).ConfigureAwait(false); StartAcceptLoop(new GenericMultiplexedConnectionListener(transport), c => multiplexedConnectionDelegate(c), listenOptions.EndpointConfig); return transport.EndPoint; } private void StartAcceptLoop<T>(IConnectionListener<T> connectionListener, Func<T, Task> connectionDelegate, EndpointConfig? endpointConfig) where T : BaseConnectionContext { var transportConnectionManager = new TransportConnectionManager(_serviceContext.ConnectionManager); var connectionDispatcher = new ConnectionDispatcher<T>(_serviceContext, connectionDelegate, transportConnectionManager); var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(connectionListener); _transports.Add(new ActiveTransport(connectionListener, acceptLoopTask, transportConnectionManager, endpointConfig)); }
SocketTransportFactory.BindAsync
public ValueTask<IConnectionListener> BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) { var transport = new SocketConnectionListener(endpoint, _options, _logger); transport.Bind(); return new ValueTask<IConnectionListener>(transport); }
internal void Bind() { if (_listenSocket != null) { throw new InvalidOperationException(SocketsStrings.TransportAlreadyBound); } Socket listenSocket; try { listenSocket = _options.CreateBoundListenSocket(EndPoint); } catch (SocketException e) when (e.SocketErrorCode == SocketError.AddressAlreadyInUse) { throw new AddressInUseException(e.Message, e); } Debug.Assert(listenSocket.LocalEndPoint != null); EndPoint = listenSocket.LocalEndPoint; listenSocket.Listen(_options.Backlog); _listenSocket = listenSocket; }
StartAcceptingConnections
在这个方法中我们调用了StartAcceptingConnectionsCore方法,这个方法中死循环调用内部定义的AcceptConnectionsAsync等待连接的方法啊,然后如果有监听到请求,就会调用KestrelConnection这个类,这个类实现了IThreadPoolWorkItem接口,所有就会调用ExecuteAsync方法,在这个方法中就会去执行我们上面UseHttpServer里面的ConnectionDelegate的委托,也就是OnConnectionAsync方法,去处理我们的请求,然后调用ProcessRequestsAsync方法。
public Task StartAcceptingConnections(IConnectionListener<T> listener) { ThreadPool.UnsafeQueueUserWorkItem(StartAcceptingConnectionsCore, listener, preferLocal: false); return _acceptLoopTcs.Task; } private void StartAcceptingConnectionsCore(IConnectionListener<T> listener) { // REVIEW: Multiple accept loops in parallel? _ = AcceptConnectionsAsync(); async Task AcceptConnectionsAsync() { try { while (true) { var connection = await listener.AcceptAsync(); if (connection == null) { // We're done listening break; } // Add the connection to the connection manager before we queue it for execution var id = _transportConnectionManager.GetNewConnectionId(); var kestrelConnection = new KestrelConnection<T>( id, _serviceContext, _transportConnectionManager, _connectionDelegate, connection, Log); _transportConnectionManager.AddConnection(id, kestrelConnection); Log.ConnectionAccepted(connection.ConnectionId); KestrelEventSource.Log.ConnectionQueuedStart(connection); ThreadPool.UnsafeQueueUserWorkItem(kestrelConnection, preferLocal: false); } } catch (Exception ex) { // REVIEW: If the accept loop ends should this trigger a server shutdown? It will manifest as a hang Log.LogCritical(0, ex, "The connection listener failed to accept any new connections."); } finally { _acceptLoopTcs.TrySetResult(); } } }
void IThreadPoolWorkItem.Execute() { _ = ExecuteAsync(); } internal async Task ExecuteAsync() { var connectionContext = _transportConnection; try { KestrelEventSource.Log.ConnectionQueuedStop(connectionContext); Logger.ConnectionStart(connectionContext.ConnectionId); KestrelEventSource.Log.ConnectionStart(connectionContext); using (BeginConnectionScope(connectionContext)) { try { await _connectionDelegate(connectionContext); } catch (Exception ex) { Logger.LogError(0, ex, "Unhandled exception while processing {ConnectionId}.", connectionContext.ConnectionId); } } } finally { await FireOnCompletedAsync(); Logger.ConnectionStop(connectionContext.ConnectionId); KestrelEventSource.Log.ConnectionStop(connectionContext); // Dispose the transport connection, this needs to happen before removing it from the // connection manager so that we only signal completion of this connection after the transport // is properly torn down. await connectionContext.DisposeAsync(); _transportConnectionManager.RemoveConnection(_id); } }
ProcessRequestsAsync
在这个方法中,他会根据我们的Http版本,创建不同的IRequestProcessor对象,在这个接口中有ProcessRequestsAsync方法,我们的请求都会进入这个方法,在这个方法,不管是http哪个版本最终都会调用到其所拥有的ProcessRequestsAsync方法中去,这里我们着重考虑这个方法具体是干了什么,还记得我们在上面传入的IHttpApplication的对象,这个其实就是我们在GenericWebHostService调用Server的StartAsync方法之前定义的IHttpApplication这个接口的实例,这个接口有一个三个方法,CreateContext,ProcessRequestAsync,DisposeContext顾名思义,Context都是构建这个泛型接口的泛型实例,这里面包含了HttpContext,以及用完后的释放,中间哪个则是去调用我们的请求管道处理,我们之前讲过,我们ApplicationBuilder调用Build方法之后,将多个管道结合成一个RequestDelegate,传入到这个接口的实现中去,然后我们在这个方法则依次调用我们的中间件管道,从而会走到各种中间件,中间件这里我主要讲一下UseEndpoing以及UseRouteing这两个中间件,
public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> httpApplication) where TContext : notnull { try { // Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs. _timeoutControl.Initialize(_systemClock.UtcNowTicks); IRequestProcessor? requestProcessor = null; switch (SelectProtocol()) { case HttpProtocols.Http1: // _http1Connection must be initialized before adding the connection to the connection manager requestProcessor = _http1Connection = new Http1Connection<TContext>((HttpConnectionContext)_context); _protocolSelectionState = ProtocolSelectionState.Selected; break; case HttpProtocols.Http2: // _http2Connection must be initialized before yielding control to the transport thread, // to prevent a race condition where _http2Connection.Abort() is called just
请发表评论