.NET Core开发日志——HttpContext
之前的文章记述了的请求处理过程。现在该聊聊如何生成ASP.NET中我们所熟悉的HttpContext。
当KestrelServer启动时,会绑定相应的IP地址,同时在绑定时将加入HttpConnectionMiddleware作为终端连接的中间件。
public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) { try { ... 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); } ... }
public static IConnectionBuilder UseHttpServer<TContext>(this IConnectionBuilder builder, IList<IConnectionAdapter> adapters, ServiceContext serviceContext, IHttpApplication<TContext> application, HttpProtocols protocols) { var middleware = new HttpConnectionMiddleware<TContext>(adapters, serviceContext, application, protocols); return builder.Use(next => { return middleware.OnConnectionAsync; }); }
当请求抵达此中间件时,在其OnConnectionAsync方法里会创建HttpConnection对象,并通过该对象处理请求。
public async Task OnConnectionAsync(ConnectionContext connectionContext) { ... var connection = new HttpConnection(httpConnectionContext); _serviceContext.ConnectionManager.AddConnection(httpConnectionId, connection); try { var processingTask = connection.ProcessRequestsAsync(_application); ... } ... }
ProcessRequestsAsync方法内部会根据HTTP协议的不同创建Http1Connection或者Http2Connection对象,一般为Http1Connection。
public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> httpApplication) { try { ... lock (_protocolSelectionLock) { // Ensure that the connection hasn't already been stopped. if (_protocolSelectionState == ProtocolSelectionState.Initializing) { switch (SelectProtocol()) { case HttpProtocols.Http1: // _http1Connection must be initialized before adding the connection to the connection manager requestProcessor = _http1Connection = CreateHttp1Connection(_adaptedTransport, application); _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 as // _http2Connection is about to be initialized. requestProcessor = CreateHttp2Connection(_adaptedTransport, application); _protocolSelectionState = ProtocolSelectionState.Selected; break; case HttpProtocols.None: // An error was already logged in SelectProtocol(), but we should close the connection. Abort(ex: null); break; default: // SelectProtocol() only returns Http1, Http2 or None. throw new NotSupportedException($"{nameof(SelectProtocol)} returned something other than Http1, Http2 or None."); } _requestProcessor = requestProcessor; } } if (requestProcessor != null) { await requestProcessor.ProcessRequestsAsync(httpApplication); } await adaptedPipelineTask; await _socketClosedTcs.Task; } ... }
Http1Connection父类HttpProtocol里的ProcessRequests方法会创建一个Context对象,但这还不是最终要找到的HttpContext。
private async Task ProcessRequests<TContext>(IHttpApplication<TContext> application) { // Keep-alive is default for HTTP/1.1 and HTTP/2; parsing and errors will change its value _keepAlive = true; while (_keepAlive) { ... var httpContext = application.CreateContext(this); try { KestrelEventSource.Log.RequestStart(this); // Run the application code for this request await application.ProcessRequestAsync(httpContext); if (_ioCompleted == 0) { VerifyResponseContentLength(); } } ... } }
在HostingApplication类中会看到HttpContext原来是由HttpContextFactory工厂类生成的。
public Context CreateContext(IFeatureCollection contextFeatures) { var context = new Context(); var httpContext = _httpContextFactory.Create(contextFeatures); _diagnostics.BeginRequest(httpContext, ref context); context.HttpContext = httpContext; return context; }
HttpContextFactory类才是最后的一站。
public HttpContext Create(IFeatureCollection featureCollection) { if (featureCollection == null) { throw new ArgumentNullException(nameof(featureCollection)); } var httpContext = new DefaultHttpContext(featureCollection); if (_httpContextAccessor != null) { _httpContextAccessor.HttpContext = httpContext; } var formFeature = new FormFeature(httpContext.Request, _formOptions); featureCollection.Set<IFormFeature>(formFeature); return httpContext; }
简单理了张流程图总结一下:
生成的HttpContext对象最终传递到IHttpApplication的ProcessRequestAsync方法。之后的事情便是HttpHost与HttpApplication的工作了。
那么费了这么多工夫,所生成的HttpContext究竟有什么用处呢?
先查看MSDN上对它的定义:
Encapsulates all HTTP-specific information about an individual HTTP request.
可以理解为对于每个单独的HTTP请求,其间所创建的HttpContext对象封装了全部所需的HTTP信息。
再看其包含的属性:
public abstract class HttpContext { public abstract IFeatureCollection Features { get; } public abstract HttpRequest Request { get; } public abstract HttpResponse Response { get; } public abstract ConnectionInfo Connection { get; } public abstract WebSocketManager WebSockets { get; } public abstract AuthenticationManager Authentication { get; } public abstract ClaimsPrincipal User { get; set; } public abstract IDictionary<object, object> Items { get; set; } public abstract IServiceProvider RequestServices { get; set; } public abstract CancellationToken RequestAborted { get; set; } public abstract string TraceIdentifier { get; set; } public abstract ISession Session { get; set; } public abstract void Abort(); }
请求(Request),响应(Response),会话(Session)这些与HTTP接触时最常见到的名词,都出现在HttpContext对象中。说明在处理HTTP请求时,若是需要获取这些相关信息,完全可以通过调用其属性而得到。
通过传递一个上下文环境参数,以协助获取各环节处理过程中所需的信息,在各种框架中是十分常见的作法。ASP.NET Core里的用法并无特别的创新,但其实用性还是毋庸置疑的。如果想要构建自己的框架时,不妨多参考下ASP.NET Core里的代码,毕竟它已是一个较成熟的产品,其中有许多值得借鉴的地方。
上一篇: json提取嵌套数据
下一篇: java包装类的自动装箱拆箱中对象的变化
推荐阅读
-
VS2015 搭建Asp.net core开发环境的方法
-
详解ASP.NET Core应用中如何记录和查看日志
-
.NET Core / C# 开发 IOT 嵌入式设备的个人见解
-
(14)ASP.NET Core 中的日志记录
-
Asp.Net Core2.2 源码阅读系列——控制台日志源码解析
-
ASP.NET Core Web 应用程序开发期间部署到IIS自定义主机域名并附加到进程调试
-
.NET Core开发日志——Middleware
-
.NET Core开发日志——OData
-
分享一个基于Net Core 3.1开发的模块化的项目
-
.NET Core前后端分离快速开发框架(Core.3.0+AntdVue)