之前的文章記述了 "從ASP.NET Core Module到KestrelServer" 的請求處理過程。現在該聊聊如何生成ASP.NET中我們所熟悉的HttpContext。 當KestrelServer啟動時,會綁定相應的IP地址,同時在綁定時將加入HttpConnectionMiddlewa ...
之前的文章記述了從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里的代碼,畢竟它已是一個較成熟的產品,其中有許多值得借鑒的地方。