余晖落尽暮晚霞,黄昏迟暮远山寻
本站
当前位置:网站首页 > 编程知识 > 正文

ASP.NET Core 5-HttpContext创建流程(vs2019创建asp.net)

xiyangw 2022-11-24 16:36 18 浏览 0 评论

上篇文章 ASP.NET Core 5-Kestrel源码解读 我们说到了Kestrel服务器是如何启动并接收Http请求的。本文我们将更进一步介绍下大家平时使用很频繁的一个类-HttpContext,看看它是怎样炼成的。

我们还是从Program.cs类说起:

public class Program
{
  public static void Main(string[] args)
  {
  	CreateHostBuilder(args).Build().Run();
  }
  public static IHostBuilder CreateHostBuilder(string[] args) =>
  	Host.CreateDefaultBuilder(args)
  	.ConfigureWebHostDefaults(webBuilder =>
    {
    	webBuilder.UseStartup<Startup>();
    });
}

Program类中的Host方法在执行初始化主机配置的时候,通过依赖注入为IServer注入了实现类KestrelServerImpl,

services.AddSingleton<IServer, KestrelServerImpl>();

再来回顾一下Kestrel的启动方式:

Kestrel的启动,是通过IHostedService的实现类GenericWebHostService来实现的

我们先来看下IHostedService接口:

public interface IServer : IDisposable
{
  IFeatureCollection Features { get; }
  Task StartAsync<TContext>(IHttpApplication<TContext> application,
                            CancellationToken cancellationToken) where TContext : notnull;
  Task StopAsync(CancellationToken cancellationToken);
}

对应的GenericWebHostService实现类:

namespace Microsoft.AspNetCore.Hosting
{
  internal class GenericWebHostService : IHostedService
  {
    public IServer Server { get; }
    ...
    //构造函数注入KestrelServerImpl对象
    ...
    public async Task StartAsync(CancellationToken cancellationToken)
    {
      ...
      //new一个HostingApplication对象,并传入Kestrel的StartAsync方法中
      await this.Server.StartAsync<HostingApplication.Context>(new HostingApplication(application, 
                             																																																							this.Logger, 
                             this.DiagnosticListener, 
                             this.HttpContextFactory),cancellationToken);
      ...
    }
  }
}

应用程序在启动的时候,会依次执行继承自IHostedService的StartAsync方法,Kestrel服务器就是这个时候通过GenericWebHostService的StartAsync方法调用的。

我们来回顾一下IServer接口:

public interface IServer : IDisposable
{
  IFeatureCollection Features { get; }
  Task StartAsync<TContext>(IHttpApplication<TContext> application,
                            CancellationToken cancellationToken) where TContext : notnull;
  Task StopAsync(CancellationToken cancellationToken);
}

还有它的实现类KestrelServerImpl:

namespace Microsoft.AspNetCore.Server.Kestrel.Core
{
  internal class KestrelServerImpl : IServer, IDisposable
  {
    ...
    public async Task StartAsync<TContext>(
      IHttpApplication<TContext> application,
      CancellationToken cancellationToken)
    {
      KestrelServerImpl kestrelServerImpl = this;
      try
      {
        if (!BitConverter.IsLittleEndian)
        	throw new PlatformNotSupportedException(CoreStrings.BigEndianNotSupported);
        ValidateOptions();
        _hasStarted = !kestrelServerImpl._hasStarted ? true : throw new InvalidOperationException(CoreStrings.ServerAlreadyStarted);
        ServiceContext.Heartbeat?.Start();
        AddressBindContext = new AddressBindContext()
        {
          ServerAddressesFeature = _serverAddresses,
          ServerOptions = kestrelServerImpl.Options,
          Logger = (ILogger) kestrelServerImpl.Trace,
          CreateBinding = new Func<ListenOptions, Task>(OnBind)
        };
        //BindAsync方法最终调用AddressBindContext的CreateBinding委托对应的OnBind方法
        await BindAsync(cancellationToken).ConfigureAwait(false);
      }
      ...
      async Task OnBind(ListenOptions options)
      {
        ListenOptions listenOptions;
        if ((options.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3)
        {
          ...
          options.UseHttp3Server<TContext>(this.ServiceContext, 
                                           application, 
                                           options.Protocols);
          ...
        }
        if ((options.Protocols & HttpProtocols.Http1) != HttpProtocols.Http1 
            && (options.Protocols & HttpProtocols.Http2) != HttpProtocols.Http2 
            && options.Protocols != HttpProtocols.None)
        return;
        ...
        options.UseHttpServer<TContext>(this.ServiceContext, 
                                        application, 
                                        options.Protocols);
        ...
      }
    }
    ...
  }
}

本次大家关注的重点在于StartAsync方法中范型参数的IHttpApplication<TContext>,它的实现类是HostingApplication,是在Kestrel启动的时候通过StartAsync方法传递给Kestrel的。

下面是HostingApplication源码:

internal class HostingApplication : IHttpApplication<HostingApplication.Context>
{
  ...
  // 创建request
  public Context CreateContext(IFeatureCollection contextFeatures)
  {
    Context hostContext;
    if (contextFeatures is IHostContextContainer<Context> container)
    {
      hostContext = container.HostContext;
      if (hostContext is null)
      {
        hostContext = new Context();
        container.HostContext = hostContext;
      }
    }
    else
    {
      // Server doesn't support pooling, so create a new Context
      hostContext = new Context();
    }
    HttpContext httpContext;
    if (_defaultHttpContextFactory != null)
    {
      var defaultHttpContext = (DefaultHttpContext)hostContext.HttpContext;
      if (defaultHttpContext is null)
      {
        httpContext = _defaultHttpContextFactory.Create(contextFeatures);
        hostContext.HttpContext = httpContext;
      }
      else
      {
        _defaultHttpContextFactory.Initialize(defaultHttpContext, contextFeatures);
        httpContext = defaultHttpContext;
      }
    }
    else
    {
      httpContext = _httpContextFactory.Create(contextFeatures);
      hostContext.HttpContext = httpContext;
    }
    ...
    return hostContext;
  }
  // Execute the request
  public Task ProcessRequestAsync(Context context)
  {
  	return _application(context.HttpContext);
  }
  ...
  internal class Context
  {
    public HttpContext HttpContext { get; set; }
    public IDisposable Scope { get; set; }
    public Activity Activity { get; set; }
    internal HostingRequestStartingLog StartLog { get; set; }
    public long StartTimestamp { get; set; }
    internal bool HasDiagnosticListener { get; set; }
    public bool EventLogEnabled { get; set; }
    public void Reset()
    {
      // Not resetting HttpContext here as we pool it on the Context
      Scope = null;
      Activity = null;
      StartLog = null;
      StartTimestamp = 0;
      HasDiagnosticListener = false;
      EventLogEnabled = false;
    }
  }
}

看完 ASP.NET Core 5-Kestrel源码解读 的同学已经知道,当Kestrel服务器启动后,会创建Sockert对象,并使用TransportManager类通过循环接收Socket收到的数据,并使用connection管道来处理connection。Kestrel默认的两个connection管道中间件是HttpConnectionMiddleware和ConnectionLimitMiddleware

让我们看下源码:

public static IConnectionBuilder UseHttpServer<TContext>(this IConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication<TContext> application, HttpProtocols protocols)
{
  var middleware = new HttpConnectionMiddleware<TContext>(serviceContext, 
                                                          application, 
                                                          protocols);
  return builder.Use(next =>
  {
  	return middleware.OnConnectionAsync;
  });
}

在Socket接收到数据后执行HttpConnectionMiddleware.OnConnectionAsync方法。我们来看下这个方法:

public Task OnConnectionAsync(ConnectionContext connectionContext)
{
  var memoryPoolFeature = connectionContext.Features.Get<IMemoryPoolFeature>();
  var httpConnectionContext = new HttpConnectionContext
  {
    ConnectionId = connectionContext.ConnectionId,
    ConnectionContext = connectionContext,
    Protocols = connectionContext.Features.Get<HttpProtocolsFeature>()?.HttpProtocols ?? _endpointDefaultProtocols,
    ServiceContext = _serviceContext,
    ConnectionFeatures = connectionContext.Features,
    MemoryPool = memoryPoolFeature?.MemoryPool ?? System.Buffers.MemoryPool<byte>.Shared,
    Transport = connectionContext.Transport,
    LocalEndPoint = connectionContext.LocalEndPoint as IPEndPoint,
    RemoteEndPoint = connectionContext.RemoteEndPoint as IPEndPoint
  };
  var connection = new HttpConnection(httpConnectionContext);
  return connection.ProcessRequestsAsync(_application);
}

OnConnectionAsync方法最终创建了一个HttpConnection对象,注意HttpConnection对象在调用ProcessRequestsAsync传递了一个_application参数,此参数就是HostingApplication。让我们继续看一下HostConnection拿着HostingApplication做了些什么。

public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> httpApplication)
{
  ...
  IRequestProcessor requestProcessor = null;
  switch (SelectProtocol())
  {
    case HttpProtocols.Http1:
      requestProcessor = _http1Connection = new Http1Connection<TContext>(_context);
      _protocolSelectionState = ProtocolSelectionState.Selected;
    break;
    case HttpProtocols.Http2:
      requestProcessor = new Http2Connection(_context);
      _protocolSelectionState = ProtocolSelectionState.Selected;
    break;
    case HttpProtocols.None:
      break;
    default:
      throw new NotSupportedException(#34;{nameof(SelectProtocol)} returned something other than Http1, Http2, Http3 or None.");
  }
  _requestProcessor = requestProcessor;
  if (requestProcessor != null)
  {
    ...
    await requestProcessor.ProcessRequestsAsync(httpApplication);
  }
  ...
}

原来HttpConnection.ProcessRequestsAsync方法根据Http协议的不同类型决定创建Http1Connection还是Http2Connection,目前常用的是Http1Connection。根据源码所示流程,创建Http1Connection实例后,将调用该实例的ProcessRequestsAsync方法,执行的是它的父类HttpProtocol的ProcessRequestsAsync方法。并最终在此方法中调用HostingApplication的CreateContext来创建相关的context,现在已经和HttpContext有关系了:

namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
  internal abstract class HttpProtocol : IHttpResponseControl, IHttpRequestFeature, IHttpRequestBodyDetectionFeature, IHttpResponseFeature, IHttpResponseBodyFeature, IRequestBodyPipeFeature, IHttpUpgradeFeature, IHttpConnectionFeature, IHttpRequestLifetimeFeature, IHttpRequestIdentifierFeature, IHttpRequestTrailersFeature, IHttpBodyControlFeature, IHttpMaxRequestBodySizeFeature, IEndpointFeature, IRouteValuesFeature, IFeatureCollection, IEnumerable<KeyValuePair<Type, object>>, IEnumerable
  {
    ...
    private async Task ProcessRequests<TContext>(IHttpApplication<TContext> application)
    {
      while (_keepAlive)
      {
        ...
        //注意,此application就是传递过来的HostingApplication
        //调用HostingApplication的CreateContext方法
        var context = application.CreateContext(this);
        try
        {
          ...
          // Run the application code for this request
          await application.ProcessRequestAsync(context);
          ...
        }
        ...
        catch (Exception ex)
        {
          ReportApplicationError(ex);
        }
        ...
        application.DisposeContext(context, _applicationException);
        ...
      }
    }
  }
}

注意以下代码,application就是传递过来的HostingApplication,这下我们看到了和HttpContext相关的代码了吧:

var context = application.CreateContext(this);

我们来看下HostingApplicatoin.CreateContext方法在请求到来时都做了些什么:

  // 创建request
  public Context CreateContext(IFeatureCollection contextFeatures)
  {
    Context hostContext;
    //创建Context对象
    if (contextFeatures is IHostContextContainer<Context> container)
    {
      hostContext = container.HostContext;
      if (hostContext is null)
      {
        hostContext = new Context();
        container.HostContext = hostContext;
      }
    }
    else
    {
      hostContext = new Context();
    }
    HttpContext httpContext;
    //创建HttpContext对象,并将对象赋值到HttpContextAccessor的HttpContext属性中
    if (_defaultHttpContextFactory != null)
    {
      var defaultHttpContext = (DefaultHttpContext)hostContext.HttpContext;
      if (defaultHttpContext is null)
      {
        httpContext = _defaultHttpContextFactory.Create(contextFeatures);
        hostContext.HttpContext = httpContext;
      }
      else
      {
        _defaultHttpContextFactory.Initialize(defaultHttpContext, contextFeatures);
        httpContext = defaultHttpContext;
      }
    }
    else
    {
      httpContext = _httpContextFactory.Create(contextFeatures);
      hostContext.HttpContext = httpContext;
    }
    ...
    return hostContext;
  }
  // Execute the request
  public Task ProcessRequestAsync(Context context)
  {
  	return _application(context.HttpContext);
  }
  ...
  internal class Context
  {
    public HttpContext HttpContext { get; set; }
    public IDisposable Scope { get; set; }
    public Activity Activity { get; set; }
    internal HostingRequestStartingLog StartLog { get; set; }
    public long StartTimestamp { get; set; }
    internal bool HasDiagnosticListener { get; set; }
    public bool EventLogEnabled { get; set; }
    public void Reset()
    {
      Scope = null;
      Activity = null;
      StartLog = null;
      StartTimestamp = 0;
      HasDiagnosticListener = false;
      EventLogEnabled = false;
    }
  }

CreateContext方法首先创建一个Context对象,然后调用DefaultHttpContextFactory.Create方法创建一个HttpContext对象的同时,在内部将依赖注入的IHttpContextAccessor中的HttpContextAccessor.HttpContext设置为当前创建的HttpContext,这就是你可以通过IHttpContextAccessor注入来获取HttpContext实例的原因。DefaultContextFactory创建好HttpContext对象后返回该HttpContext对象。

此时,application.ProcessRequestAsync(context)调用了HostApplication实例化时传入的中间件,进行了第一个中间件的执行HostFilteringMiddleware,然后让请求管道中的中间件依次针对传入的HttpContext做相应的处理,源码如下:

internal class HostingApplication : IHttpApplication<HostingApplication.Context>
{
  private readonly RequestDelegate _application;
  public HostingApplication(
  	RequestDelegate application,
  	ILogger logger,
  	DiagnosticListener diagnosticSource,
  	IHttpContextFactory httpContextFactory)
  {
    ...
    _application = application;
    ...
  }
  public Task ProcessRequestAsync(HostingApplication.Context context) => _application(context.HttpContext);
}

至此,HttpContext已成功创建,并在后续中间件调用Invoke(HttpContext)方法时传入HttpContext。

看到这里,大家对ASP.NET Core 5中的HttpContext的创建流程了解了吧?

文章最后,我们用一张流程图来梳理一下整个流程吧:


从这张图来看,大家是不是对HttpContext的创建流程更加了解了呢?

下一篇,我们来看一下ASP.NET Core 5中的中间件,看看它是怎么工作的

还在等什么,赶快收藏加关注吧!更多精彩内容还在等着你!感谢大家的支持!

相关推荐

Vue的框架(了解)

前端MVC设计模式MVC设计模式,其实就是将前端实现某个业务的所有代码划分为三部分Model:模型,指数据模型,这个数据一般来自于服务器View:视图,指页面标签内容Controller:控制...

Vue.js实战 第五章练习一

练习要求:在原有表格基础上,新增一项是否选中该商品的功能,总价变为只计算选中商品的总价,同时提供一个全选的按钮。实现思路:按照vue数据和dom元素双向绑定的特性,定义allCheckStatus变量...

Vue基础到进阶教程之class和style绑定

关于class和style我们并不陌生,这个在学习css的时候就是家常便饭了,操作元素的class列表和内联样式是数据绑定的一个常见需求。因为它们都是属性,所以我们可以用v-bind处理它们,...

深入Vue 必学高阶组件 HOC「进阶篇」

作者:ssh转发连接:https://mp.weixin.qq.com/s/seKoLSIMtTd1sU4uDrgZCA前言高阶组件这个概念在React中一度非常流行,但是在Vue的社区里讨论...

周末大礼包,23道高质量中级前端面试题。金九银十,建议收藏

这套面试题考察的内容比较常见,涉及到JavaScript、ES6、CSS、Vue、简单算法,浏览器相关知识等。题目列表1.JavaScript的数据类型有哪些2.什么是同源策略3.跨域的方法...

vue3.0-摒弃Object.defineProperty,基于 Proxy 的观察者机制

写在前面:11月16日早上,Vue.js的作者尤大大在VueToronto的主题演讲中预演了Vue.js3.0的一些新特性,其中一个很重要的改变就是Vue3将使用ES6的Proxy作...

程序员都必掌握的前端教程之VUE基础教程(七)

阅读本文约需要10分钟,您可以先关注我们,避免下次无法找到。本篇文章成哥继续带大家来学习前端VUE教程,今天主要讲解VUE的表单处理等知识点。下面我们就一起来学习该块内容吧!01简介在日常开发中,我...

web前端开之网站搭建框架之vue详解

网站搭建框架之vueVue是web前端快速搭建网站的框架之一。它与jQuery有所不同,是以数据驱动web界面(以操作数据改变页面,而jQuery是以操作节点来改变页面),同时,vue还实现了数据的双...

vue3.0尝鲜-基于 Proxy 的观察者机制探索

Vue.js的作者尤大大在VueToronto的主题演讲中预演了Vue.js3.0的一些新特性,其中一个很重要的改变就是Vue3将使用ES6的Proxy作为其观察者机制,取代之前使用...

TypeScript 设计模式之观察者模式

一、模式介绍1.背景介绍在软件系统中经常碰到这类需求:当一个对象的状态发生改变,某些与它相关的对象也要随之做出相应的变化。这是建立一种「对象与对象之间的依赖关系」,一个对象发生改变时将「自动通知其他...

vue面试3

1.单页面应用与多页面应用的去别2.简述一下Sass、Less,且说明区别?他们是动态的样式语言,是CSS预处理器,CSS上的一种抽象层。他们是一种特殊的语法/语言而编译成CSS。变量符不一样,les...

VUE v-bind 数据绑定

动态的绑定一个或多个attribute,也可以是组件的prop。缩写::或者.(当使用.prop修饰符)期望:any(带参数)|Object(不带参数)参数:attrOrP...

vue初学习之自定义选择框实现

v-model简单介绍在使用vue的过程中会经常用到input和textarea这类表单元素,vue对于这些元素的数据绑定和我们以前经常用的jQuery有些区别。vue使用v-model实现这些标签...

Vue实现拖拽穿梭框功能四种方式

一、使用原生js实现拖拽打开视频讲解更加详细Vue实现拖拽穿梭框功能的四种方式_哔哩哔哩_bilibili<html><head><meta...

Vue3.x setup 语法糖实现props双向绑定

1.背景为了封装一下Element-Plus的分页插件,需要实现父子组件之间的传值。2.父组件<scriptsetuplang="ts">letqueryPa...

取消回复欢迎 发表评论: