继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

.NET Core开发日志——HttpContext

烙印99
关注TA
已关注
手记 361
粉丝 92
获赞 446

之前的文章记述了从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;
}

简单理了张流程图总结一下:
https://img2.mukewang.com/5b59df3600012d7103420662.jpg

生成的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里的代码,毕竟它已是一个较成熟的产品,其中有许多值得借鉴的地方。

原文出处

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP