之前的文章记述了从 ASP.NET Core Module 到 KestrelServer 的请求处理过程. 现在该聊聊如何生成 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 里的代码, 毕竟它已是一个较成熟的产品, 其中有许多值得借鉴的地方.
来源: https://www.cnblogs.com/kenwoo/p/9369637.html