上篇文章 ASP.NET Core 5-Kestrel源码解读 我们说到了Kestrel服务器是如何启动并接收Http请求的。本文我们将更进一步介绍下大家平时使用很频繁的一个类-HttpContext,看看它是怎样炼成的。
我们还是从Program.cs类说起:
public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); }
Program类中的Host方法在执行初始化主机配置的时候,通过依赖注入为IServer注入了实现类KestrelServerImpl,
services.AddSingleton<IServer, KestrelServerImpl>();
再来回顾一下Kestrel的启动方式:
Kestrel的启动,是通过IHostedService的实现类GenericWebHostService来实现的
我们先来看下IHostedService接口:
public interface IServer : IDisposable { IFeatureCollection Features { get; } Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) where TContext : notnull; Task StopAsync(CancellationToken cancellationToken); }
对应的GenericWebHostService实现类:
namespace Microsoft.AspNetCore.Hosting { internal class GenericWebHostService : IHostedService { public IServer Server { get; } ... //构造函数注入KestrelServerImpl对象 ... public async Task StartAsync(CancellationToken cancellationToken) { ... //new一个HostingApplication对象,并传入Kestrel的StartAsync方法中 await this.Server.StartAsync<HostingApplication.Context>(new HostingApplication(application, this.Logger, this.DiagnosticListener, this.HttpContextFactory),cancellationToken); ... } } }
应用程序在启动的时候,会依次执行继承自IHostedService的StartAsync方法,Kestrel服务器就是这个时候通过GenericWebHostService的StartAsync方法调用的。
我们来回顾一下IServer接口:
public interface IServer : IDisposable { IFeatureCollection Features { get; } Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) where TContext : notnull; Task StopAsync(CancellationToken cancellationToken); }
还有它的实现类KestrelServerImpl:
namespace Microsoft.AspNetCore.Server.Kestrel.Core { internal class KestrelServerImpl : IServer, IDisposable { ... public async Task StartAsync<TContext>( IHttpApplication<TContext> application, CancellationToken cancellationToken) { KestrelServerImpl kestrelServerImpl = this; try { if (!BitConverter.IsLittleEndian) throw new PlatformNotSupportedException(CoreStrings.BigEndianNotSupported); ValidateOptions(); _hasStarted = !kestrelServerImpl._hasStarted ? true : throw new InvalidOperationException(CoreStrings.ServerAlreadyStarted); ServiceContext.Heartbeat?.Start(); AddressBindContext = new AddressBindContext() { ServerAddressesFeature = _serverAddresses, ServerOptions = kestrelServerImpl.Options, Logger = (ILogger) kestrelServerImpl.Trace, CreateBinding = new Func<ListenOptions, Task>(OnBind) }; //BindAsync方法最终调用AddressBindContext的CreateBinding委托对应的OnBind方法 await BindAsync(cancellationToken).ConfigureAwait(false); } ... async Task OnBind(ListenOptions options) { ListenOptions listenOptions; if ((options.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3) { ... options.UseHttp3Server<TContext>(this.ServiceContext, application, options.Protocols); ... } if ((options.Protocols & HttpProtocols.Http1) != HttpProtocols.Http1 && (options.Protocols & HttpProtocols.Http2) != HttpProtocols.Http2 && options.Protocols != HttpProtocols.None) return; ... options.UseHttpServer<TContext>(this.ServiceContext, application, options.Protocols); ... } } ... } }
本次大家关注的重点在于StartAsync方法中范型参数的IHttpApplication<TContext>,它的实现类是HostingApplication,是在Kestrel启动的时候通过StartAsync方法传递给Kestrel的。
下面是HostingApplication源码:
internal class HostingApplication : IHttpApplication<HostingApplication.Context> { ... // 创建request public Context CreateContext(IFeatureCollection contextFeatures) { Context hostContext; if (contextFeatures is IHostContextContainer<Context> container) { hostContext = container.HostContext; if (hostContext is null) { hostContext = new Context(); container.HostContext = hostContext; } } else { // Server doesn't support pooling, so create a new Context hostContext = new Context(); } HttpContext httpContext; if (_defaultHttpContextFactory != null) { var defaultHttpContext = (DefaultHttpContext)hostContext.HttpContext; if (defaultHttpContext is null) { httpContext = _defaultHttpContextFactory.Create(contextFeatures); hostContext.HttpContext = httpContext; } else { _defaultHttpContextFactory.Initialize(defaultHttpContext, contextFeatures); httpContext = defaultHttpContext; } } else { httpContext = _httpContextFactory.Create(contextFeatures); hostContext.HttpContext = httpContext; } ... return hostContext; } // Execute the request public Task ProcessRequestAsync(Context context) { return _application(context.HttpContext); } ... internal class Context { public HttpContext HttpContext { get; set; } public IDisposable Scope { get; set; } public Activity Activity { get; set; } internal HostingRequestStartingLog StartLog { get; set; } public long StartTimestamp { get; set; } internal bool HasDiagnosticListener { get; set; } public bool EventLogEnabled { get; set; } public void Reset() { // Not resetting HttpContext here as we pool it on the Context Scope = null; Activity = null; StartLog = null; StartTimestamp = 0; HasDiagnosticListener = false; EventLogEnabled = false; } } }
看完 ASP.NET Core 5-Kestrel源码解读 的同学已经知道,当Kestrel服务器启动后,会创建Sockert对象,并使用TransportManager类通过循环接收Socket收到的数据,并使用connection管道来处理connection。Kestrel默认的两个connection管道中间件是HttpConnectionMiddleware和ConnectionLimitMiddleware
让我们看下源码:
public static IConnectionBuilder UseHttpServer<TContext>(this IConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication<TContext> application, HttpProtocols protocols) { var middleware = new HttpConnectionMiddleware<TContext>(serviceContext, application, protocols); return builder.Use(next => { return middleware.OnConnectionAsync; }); }
在Socket接收到数据后执行HttpConnectionMiddleware.OnConnectionAsync方法。我们来看下这个方法:
public Task OnConnectionAsync(ConnectionContext connectionContext) { var memoryPoolFeature = connectionContext.Features.Get<IMemoryPoolFeature>(); var httpConnectionContext = new HttpConnectionContext { ConnectionId = connectionContext.ConnectionId, ConnectionContext = connectionContext, Protocols = connectionContext.Features.Get<HttpProtocolsFeature>()?.HttpProtocols ?? _endpointDefaultProtocols, ServiceContext = _serviceContext, ConnectionFeatures = connectionContext.Features, MemoryPool = memoryPoolFeature?.MemoryPool ?? System.Buffers.MemoryPool<byte>.Shared, Transport = connectionContext.Transport, LocalEndPoint = connectionContext.LocalEndPoint as IPEndPoint, RemoteEndPoint = connectionContext.RemoteEndPoint as IPEndPoint }; var connection = new HttpConnection(httpConnectionContext); return connection.ProcessRequestsAsync(_application); }
OnConnectionAsync方法最终创建了一个HttpConnection对象,注意HttpConnection对象在调用ProcessRequestsAsync传递了一个_application参数,此参数就是HostingApplication。让我们继续看一下HostConnection拿着HostingApplication做了些什么。
public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> httpApplication) { ... IRequestProcessor requestProcessor = null; switch (SelectProtocol()) { case HttpProtocols.Http1: requestProcessor = _http1Connection = new Http1Connection<TContext>(_context); _protocolSelectionState = ProtocolSelectionState.Selected; break; case HttpProtocols.Http2: requestProcessor = new Http2Connection(_context); _protocolSelectionState = ProtocolSelectionState.Selected; break; case HttpProtocols.None: break; default: throw new NotSupportedException(#34;{nameof(SelectProtocol)} returned something other than Http1, Http2, Http3 or None."); } _requestProcessor = requestProcessor; if (requestProcessor != null) { ... await requestProcessor.ProcessRequestsAsync(httpApplication); } ... }
原来HttpConnection.ProcessRequestsAsync方法根据Http协议的不同类型决定创建Http1Connection还是Http2Connection,目前常用的是Http1Connection。根据源码所示流程,创建Http1Connection实例后,将调用该实例的ProcessRequestsAsync方法,执行的是它的父类HttpProtocol的ProcessRequestsAsync方法。并最终在此方法中调用HostingApplication的CreateContext来创建相关的context,现在已经和HttpContext有关系了:
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { internal abstract class HttpProtocol : IHttpResponseControl, IHttpRequestFeature, IHttpRequestBodyDetectionFeature, IHttpResponseFeature, IHttpResponseBodyFeature, IRequestBodyPipeFeature, IHttpUpgradeFeature, IHttpConnectionFeature, IHttpRequestLifetimeFeature, IHttpRequestIdentifierFeature, IHttpRequestTrailersFeature, IHttpBodyControlFeature, IHttpMaxRequestBodySizeFeature, IEndpointFeature, IRouteValuesFeature, IFeatureCollection, IEnumerable<KeyValuePair<Type, object>>, IEnumerable { ... private async Task ProcessRequests<TContext>(IHttpApplication<TContext> application) { while (_keepAlive) { ... //注意,此application就是传递过来的HostingApplication //调用HostingApplication的CreateContext方法 var context = application.CreateContext(this); try { ... // Run the application code for this request await application.ProcessRequestAsync(context); ... } ... catch (Exception ex) { ReportApplicationError(ex); } ... application.DisposeContext(context, _applicationException); ... } } } }
注意以下代码,application就是传递过来的HostingApplication,这下我们看到了和HttpContext相关的代码了吧:
var context = application.CreateContext(this);
我们来看下HostingApplicatoin.CreateContext方法在请求到来时都做了些什么:
// 创建request public Context CreateContext(IFeatureCollection contextFeatures) { Context hostContext; //创建Context对象 if (contextFeatures is IHostContextContainer<Context> container) { hostContext = container.HostContext; if (hostContext is null) { hostContext = new Context(); container.HostContext = hostContext; } } else { hostContext = new Context(); } HttpContext httpContext; //创建HttpContext对象,并将对象赋值到HttpContextAccessor的HttpContext属性中 if (_defaultHttpContextFactory != null) { var defaultHttpContext = (DefaultHttpContext)hostContext.HttpContext; if (defaultHttpContext is null) { httpContext = _defaultHttpContextFactory.Create(contextFeatures); hostContext.HttpContext = httpContext; } else { _defaultHttpContextFactory.Initialize(defaultHttpContext, contextFeatures); httpContext = defaultHttpContext; } } else { httpContext = _httpContextFactory.Create(contextFeatures); hostContext.HttpContext = httpContext; } ... return hostContext; } // Execute the request public Task ProcessRequestAsync(Context context) { return _application(context.HttpContext); } ... internal class Context { public HttpContext HttpContext { get; set; } public IDisposable Scope { get; set; } public Activity Activity { get; set; } internal HostingRequestStartingLog StartLog { get; set; } public long StartTimestamp { get; set; } internal bool HasDiagnosticListener { get; set; } public bool EventLogEnabled { get; set; } public void Reset() { Scope = null; Activity = null; StartLog = null; StartTimestamp = 0; HasDiagnosticListener = false; EventLogEnabled = false; } }
CreateContext方法首先创建一个Context对象,然后调用DefaultHttpContextFactory.Create方法创建一个HttpContext对象的同时,在内部将依赖注入的IHttpContextAccessor中的HttpContextAccessor.HttpContext设置为当前创建的HttpContext,这就是你可以通过IHttpContextAccessor注入来获取HttpContext实例的原因。DefaultContextFactory创建好HttpContext对象后返回该HttpContext对象。
此时,application.ProcessRequestAsync(context)调用了HostApplication实例化时传入的中间件,进行了第一个中间件的执行HostFilteringMiddleware,然后让请求管道中的中间件依次针对传入的HttpContext做相应的处理,源码如下:
internal class HostingApplication : IHttpApplication<HostingApplication.Context> { private readonly RequestDelegate _application; public HostingApplication( RequestDelegate application, ILogger logger, DiagnosticListener diagnosticSource, IHttpContextFactory httpContextFactory) { ... _application = application; ... } public Task ProcessRequestAsync(HostingApplication.Context context) => _application(context.HttpContext); }
至此,HttpContext已成功创建,并在后续中间件调用Invoke(HttpContext)方法时传入HttpContext。
看到这里,大家对ASP.NET Core 5中的HttpContext的创建流程了解了吧?
文章最后,我们用一张流程图来梳理一下整个流程吧:

从这张图来看,大家是不是对HttpContext的创建流程更加了解了呢?
下一篇,我们来看一下ASP.NET Core 5中的中间件,看看它是怎么工作的
还在等什么,赶快收藏加关注吧!更多精彩内容还在等着你!感谢大家的支持!