• .NET Core开发日志——HttpContext


    之前的文章记述了从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方法。之后的事情便是WebHost与HostingApplication的工作了。

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

  • 相关阅读:
    纯JS.CSS编写的可拖拽并左右分栏的插件(复制代码就能用)
    jquery on()方法重复绑定解决方法
    在PHP语言中使用JSON和将json还原成数组
    Flex 布局教程:语法篇
    在线生成大全(这里真的什么都有)
    css3(border-radius)边框圆角详解
    css常用鼠标指针形状代码
    input 正则限制输入内容
    html中input标签的tabindex属性
    CSS gradient渐变之webkit核心浏览器下的使用
  • 原文地址:https://www.cnblogs.com/kenwoo/p/9369637.html
Copyright © 2020-2023  润新知