2

.NET源码解读kestrel服务器及创建HttpContext对象流程

 11 months ago
source link: https://www.cnblogs.com/Z7TS/p/17459777.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

.NET本身就是一个基于中间件(middleware)的框架,它通过一系列的中间件组件来处理HTTP请求和响应。因此,本篇文章主要描述从用户键入请求到服务器响应的大致流程,并深入探讨.NET通过kestrel将HTTP报文转换为HttpContext对象。

通过本文,您可以了解以下内容:

  • http的数据流转流程
  • 源码解读kestrel服务器的运作流程及生成HttpContext对象

一、HTTP请求的数据流转过程

1. 数据流转

HTTP 请求的数据流转过程非常复杂,涉及多个协议层次和网络设备。通过数据流转示意图可以简要了解该流程:

1148127-20230612174422755-1202553502.png
  1. DNS 解析

客户端浏览器会首先尝试从本地缓存中查找目标服务器的 IP 地址。如果缓存中没有该域名对应的 IP 地址,则会向本地 DNS 服务器发起 DNS 查询请求。

DNS 服务器会根据域名信息向上级 DNS 服务器发送递归查询请求,直到找到能够返回该域名对应 IP 地址的 DNS 服务器为止。最终,DNS 服务器将目标服务器的 IP 地址返回给客户端浏览器。

2.TCP 连接

TCP 连接需要经过三次握手的过程:

  • 第一次握手:客户端发送 SYN 包,表示请求建立连接。
  • 第二次握手:服务器返回 SYN+ACK 包,表示同意建立连接。
  • 第三次握手:客户端发送 ACK 包,表示确认连接已建立。

当客户端和服务器完成三次握手后,TCP 连接就建立成功了。

  1. 应用层发送HTTP请求

用户在浏览器中输入URL后,浏览器会向应用层发送HTTP请求。请求报文包含请求方法、URI、协议版本和请求头信息等。

  1. 传输层封装TCP协议数据段

传输层负责将HTTP请求报文分成若干个数据段进行传输,并使用TCP协议对这些数据段进行封装。

  1. 网络层路由选择和寻址

网络层负责对TCP数据段进行分组,并通过IP协议进行路由选择和寻址,以便将数据包从本地网络送到目标服务器。

  1. 数据链路层封装数据帧

数据链路层将IP数据包封装为数据帧,并添加源和目标MAC地址,以便在物理层上进行传输。

  1. 物理层传输比特流

物理层将数据帧转换为比特流,并通过物理介质(如网线、无线电波等)将数据发送到目标服务器。

  1. 服务器接收HTTP请求

当数据包到达目标服务器后,网络协议栈会解析数据包,并将HTTP请求报文交给Web服务器处理。

  1. .NET服务器处理HTTP请求

Web服务器处理HTTP请求,包括解析HTTP请求报文、映射URL到相应的处理器、执行请求处理程序,并生成HTTP响应报文等。

  1. 传输层封装TCP协议数据段

Web服务器生成HTTP响应报文之后,通过TCP协议将响应数据分成若干个数据段进行封装。

  1. 数据链路层封装数据帧

数据链路层将TCP数据段封装为数据帧,并添加源和目标MAC地址。

  1. 物理层传输比特流

物理层将数据帧转换为比特流,并通过物理介质(如网线、无线电波等)将数据发送回客户端浏览器。

  1. 应用层接收HTTP响应

客户端浏览器收到HTTP响应报文后,会交给应用层进行解析和处理。响应报文包含状态行、响应头和响应体等信息。

通过上文,我们已经了解了 HTTP 请求数据流转的基本过程。下图展示了数据从 HTTP 数据开始,逐层添加 TCP、IP、以太网头部,然后在每个层次进行解析,最终抵达目标服务器。

1148127-20230612181300638-275042570.png

2. 报文数据格式

下边贴一张网络包的报文数据格式图:

1148127-20230612181806967-2038436381.png

想深入了解更多计算机网络知识的同学,可以自行查阅书籍和资料,这里有位博主总结的很好,地址:小林coding

二、认识kestrel和HttpContext

1. kestrel的作用

Kestrel 是一个基于libuv的跨平台Web 服务器,是.NET中默认启用的 Web 服务器,可以处理来自客户端的 HTTP 请求和响应。

图一 内网访问程序

1148127-20230614110011173-2094962030.png

图二 反向代理访问程序

1148127-20230614110019053-2010466013.png

2. 什么是HttpContext?

HttpContext保存有关 Http 请求的当前信息。它包含授权,身份验证,请求,响应,会话,项目,用户,表单选项等信息。收到每个 HTTP 请求时,HttpContext都会初始化一个包含当前信息的新对象。

想要了解更多HttpContext对象的属性和方法,请直接参阅官方文档

3. .NET中如何访问HttpContext

  • 使用 .NET Core 内置依赖项注入容器注册依赖项,如下所示的 Startup.cs配置服务类方法:
C#
// 注入IHttpContextAccessor服务
builder.Services.AddHttpContextAccessor();

// 自定义服务中访问HttpContext
public class UserRepository : IUserRepository
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public UserRepository(IHttpContextAccessor httpContextAccessor) =>
        _httpContextAccessor = httpContextAccessor;

    public void LogCurrentUser()
    {
        var username = _httpContextAccessor.HttpContext.User.Identity.Name;

        // ...
    }
}

更多的访问方式请自行查阅官方文档

三、源码解读kestrel创建HttpContext对象

以下是源代码的部分删减和修改,以便于更好地理解

1. 创建主机构建器

我们从Program开始,使用CreateBuilder方法创建一个默认的主机构建器,配置应用程序的默认设置以及注入基础服务。

C#
// 在Program.cs文件中调用
var builder = WebApplication.CreateBuilder(args);

// CreateBuilder方法返回了WebApplicationBuilder实例
public static WebApplicationBuilder CreateBuilder(string[] args) =>
    new WebApplicationBuilder(new WebApplicationOptions(){ Args = args });

在WebApplicationBuilder 类的构造函数中,关于配置Configuration和IOC容器相关的已经在历史文章中做过解读。本文在看下几个主机构建器的关系和作用:

  • BootstrapHostBuilder 是一个基本的主机构建器,构建默认的主机(Host)和服务容器(Service Container)

  • IHostBuilder 定义了一组用于配置主机的方法,并返回一个IHost实例。使用IHostBuilder可以自定义应用程序的配置信息,如应用程序的环境、日志记录、配置文件等

  • ConfigureHostBuilder 扩展了 IHostBuilder 接口,并添加了一些特定主机的配置选项,例如应用程序名称、配置文件路径、日志、依赖注入等,可以根据需要进行扩展和定制。

  • ConfigureWebHostBuilder 是 ConfigureHostBuilder 的子类,主要用于处理与 Web 主机相关的配置,例如 Kestrel 服务器选项、HTTPS 配置、Web 根目录等

这几个的关系简单来讲就是通过BootstrapHostBuilder和IHostBuilder创建主机构建器,然后使用ConfigureHostBuilder和ConfigureWebHostBuilder扩展方法设置所需的选项,最终创建主机和服务容器实例

C#
internal WebApplicationBuilder(WebApplicationOptions options, Action<IHostBuilder>? configureDefaults = null)
{
    // configuration将在后续的配置中提供应用程序选项和参数
    var configuration = new ConfigurationManager();
    configuration.AddEnvironmentVariables(prefix: "ASPNETCORE_");

    // 创建一个 HostApplicationBuilder 对象,并将其中包含的设置初始化为从 WebApplicationOptions 对象中获取的值
    _hostApplicationBuilder = new HostApplicationBuilder(new HostApplicationBuilderSettings
    {
        Args = options.Args,
        ApplicationName = options.ApplicationName,
        EnvironmentName = options.EnvironmentName,
        ContentRootPath = options.ContentRootPath,
        Configuration = configuration,
    });

    // 创建BootstrapHostBuilder实例
    var bootstrapHostBuilder = new BootstrapHostBuilder(_hostApplicationBuilder);

    // bootstrapHostBuilder 上调用 ConfigureWebHostDefaults 方法,以进行特定于 Web 主机的配置
    bootstrapHostBuilder.ConfigureWebHostDefaults(webHostBuilder =>
    {
      //......
    });

    var webHostContext = (WebHostBuilderContext)bootstrapHostBuilder.Properties[typeof(WebHostBuilderContext)];
    Environment = webHostContext.HostingEnvironment;

    Host = new ConfigureHostBuilder(bootstrapHostBuilder.Context, Configuration, Services);
    WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services);
}

使用Kestrel构建默认主机

C#
internal static void ConfigureWebDefaults(IWebHostBuilder builder)
{
    ConfigureWebDefaultsWorker(
        builder.UseKestrel(ConfigureKestrel),
        services =>
        {
            services.AddRouting();
        });
}

public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder, Action<WebHostBuilderContext, KestrelServerOptions> configureOptions)
{
    return hostBuilder.UseKestrel().ConfigureKestrel(configureOptions);
}

配置WebHost在Kestrel服务器上运行,并通过QUIC协议实现高效数据传输的方式

C#
public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder)
{
    return hostBuilder
        .UseKestrelCore()
        .UseKestrelHttpsConfiguration()
        .UseQuic(options =>
        {
            // Configure server defaults to match client defaults.
            // https://github.com/dotnet/runtime/blob/a5f3676cc71e176084f0f7f1f6beeecd86fbeafc/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs#L118-L119
            options.DefaultStreamErrorCode = (long)Http3ErrorCode.RequestCancelled;
            options.DefaultCloseErrorCode = (long)Http3ErrorCode.NoError;
        });
}

重点看下UseKestrelCore方法,该方法将Kestrel服务器应用到主机构建器的上下文中,并配置相关的服务

  • IConnectionListenerFactory:负责创建和管理传输连接
  • KestrelServerOptions:负责配置Kestrel服务器选项,例如端口号、连接数等
  • IHttpsConfigurationService:负责HTTPS支持,例如配置证书、加密算法等
  • IServer:指定KestrelServerImpl作为其实现类。这个服务是Kestrel服务器的核心实现,它接收来自客户端的请求并返回响应
  • KestrelMetrics:收集和报告有关Kestrel服务器运行状况的数据
C#
public static IWebHostBuilder UseKestrelCore(this IWebHostBuilder hostBuilder)
{
    hostBuilder.ConfigureServices(services =>
    {
        // Don't override an already-configured transport
        services.TryAddSingleton<IConnectionListenerFactory, SocketTransportFactory>();

        services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();
        services.AddSingleton<IHttpsConfigurationService, HttpsConfigurationService>();
        services.AddSingleton<IServer, KestrelServerImpl>();
        services.AddSingleton<KestrelMetrics>();
    });
    return hostBuilder;
}

2. 启动主机,并侦听HTTP请求

从Program中app.Run()开始,启动主机,最终会调用IHost的StartAsync方法。

C#
app.Run();

public void Run([StringSyntax(StringSyntaxAttribute.Uri)] string? url = null)
{
    Listen(url);
    HostingAbstractionsHostExtensions.Run(this);
}

public static async Task RunAsync(this IHost host, CancellationToken token = default)
{
    try
    {
        await host.StartAsync(token).ConfigureAwait(false);

        await host.WaitForShutdownAsync(token).ConfigureAwait(false);
    }
    finally
    {
        if (host is IAsyncDisposable asyncDisposable)
        {
            await asyncDisposable.DisposeAsync().ConfigureAwait(false);
        }
        else
        {
            host.Dispose();
        }
    }
}

将中间件和StartupFilters扩展传入HostingApplication主机,并进行启动

C#
public async Task StartAsync(CancellationToken cancellationToken)
{
    // ...省略了从配置中获取服务器监听地址和端口...

    // 这个东西就是中间件,下篇文章再重点解读
    RequestDelegate? application = null;
    try
    {
        IApplicationBuilder builder = ApplicationBuilderFactory.CreateBuilder(Server.Features);

        foreach (var filter in StartupFilters.Reverse())
        {
            configure = filter.Configure(configure);
        }
        configure(builder);
        // Build the request pipeline
        application = builder.Build();
    }
    catch (Exception ex)
    {
        Logger.ApplicationError(ex);
    }

    /*
     * application:中间件
     * DiagnosticListener:事件监听器
     * HttpContextFactory:HttpContext对象的工厂
     */
    HostingApplication httpApplication = new HostingApplication(application, Logger, DiagnosticListener, ActivitySource, Propagator, HttpContextFactory, HostingEventSource.Log, HostingMetrics);

    await Server.StartAsync(httpApplication, cancellationToken);

}

KestrelServerImpl类中实现Server.StartAsync方法,用于在指定地址和端口上开启HTTP服务。本篇文章只会解读http2的实现流程,http3的如果您感兴趣,请自行查阅源码。

C#
public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) where TContext : notnull
{
      // 用于处理与绑定事件相关的逻辑
      async Task OnBind(ListenOptions options, CancellationToken onBindCancellationToken)
      {
          // ...省略 获取是否支持Http1/2/3/协议及TLS加密,及判断至少支持一种协议...

          if (hasHttp1 || hasHttp2
              || options.Protocols == HttpProtocols.None)
          {
              // 调用UseHttpServer方法,为HTTP连接配置中间件、应用程序请求处理成中间件
              options.UseHttpServer(ServiceContext, application, options.Protocols, addAltSvcHeader);
              ConnectionDelegate connectionDelegate = options.Build();

              // 添加连接限制中间件
              connectionDelegate = EnforceConnectionLimit(connectionDelegate, Options.Limits.MaxConcurrentConnections, Trace, ServiceContext.Metrics);

              // 开始监听指定地址和端口上的HTTP请求
              options.EndPoint = await _transportManager.BindAsync(configuredEndpoint, connectionDelegate, options.EndpointConfig, onBindCancellationToken).ConfigureAwait(false);
          }

          //...省略http3...
      }

      AddressBindContext = new AddressBindContext(_serverAddresses, Options, Trace, OnBind);

      await BindAsync(cancellationToken).ConfigureAwait(false);
}

UseHttpServer方法是将创建连接,解析等功能创建成委托中间件。在_transportManager.BindAsync方法中,启动监听后执行。我们先跳过UseHttpServer方法,先看下启动监听的方法。

C#
public async Task<EndPoint> BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig? endpointConfig, CancellationToken cancellationToken)
{
    // 遍历所有的ITransportFactory对象,并查找可以对指定地址和端口进行绑定的工厂对象
    foreach (var transportFactory in _transportFactories)
    {
        var selector = transportFactory as IConnectionListenerFactorySelector;
        if (CanBindFactory(endPoint, selector))
        {
            // 调用其BindAsync方法,在指定地址和端口上启动传输通道(Transport)
            var transport = await transportFactory.BindAsync(endPoint, cancellationToken).ConfigureAwait(false);

            // 启动循环接收传入连接。对于每个新连接请求,ConnectionListener都会创建一个新的ConnectionContext对象,并将其传递给连接处理委托(ConnectionDelegate)进行处理
            StartAcceptLoop(new GenericConnectionListener(transport), c => connectionDelegate(c), endpointConfig);
            return transport.EndPoint;
        }
    }
}

该方法使用IConnectionListener接口创建一个新的连接监听器(ConnectionListener),并启动一个循环以便不断接收传入的连接请求。对于每个新连接请求,它都会创建一个新的BaseConnectionContext对象,并将其传递给连接处理委托进行相应的操作

C#
private void StartAcceptLoop<T>(IConnectionListener<T> connectionListener, Func<T, Task> connectionDelegate, EndpointConfig? endpointConfig) where T : BaseConnectionContext
{
    var transportConnectionManager = new TransportConnectionManager(_serviceContext.ConnectionManager);

    var connectionDispatcher = new ConnectionDispatcher<T>(_serviceContext, connectionDelegate, transportConnectionManager);

    var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(connectionListener);

    _transports.Add(new ActiveTransport(connectionListener, acceptLoopTask, transportConnectionManager, endpointConfig));
}

线程池中通过while循环不断监听连接请求

C#
public Task StartAcceptingConnections(IConnectionListener<T> listener)
{
    ThreadPool.UnsafeQueueUserWorkItem(StartAcceptingConnectionsCore, listener, preferLocal: false);
    return _acceptLoopTcs.Task;
}
private void StartAcceptingConnectionsCore(IConnectionListener<T> listener)
{
    // REVIEW: Multiple accept loops in parallel?
    _ = AcceptConnectionsAsync();

    async Task AcceptConnectionsAsync()
    {
        try
        {
            while (true)
            {
                var connection = await listener.AcceptAsync();
                if (connection == null)
                {
                    // We're done listening
                    break;
                }
                // 创建一个新的连接Id
                var id = _transportConnectionManager.GetNewConnectionId();

                var metricsContext = Metrics.CreateContext(connection);

                var kestrelConnection = new KestrelConnection<T>(
                    id, _serviceContext, _transportConnectionManager, _connectionDelegate, connection, Log, metricsContext);

                _transportConnectionManager.AddConnection(id, kestrelConnection);
              
                Metrics.ConnectionQueuedStart(metricsContext);

                ThreadPool.UnsafeQueueUserWorkItem(kestrelConnection, preferLocal: false);
            }
        }
    }
}

IThreadPoolWorkItem执行方法就是调用了我们上文中,先跳过的委托部分

C#
void IThreadPoolWorkItem.Execute()
{
    using (BeginConnectionScope(connectionContext))
    {
        try
        {
            await _connectionDelegate(connectionContext);
        }
        catch (Exception ex)
        {
        }
    }
}

回到上文中的UseHttpServer方法,该方法中创建HttpConnectionMiddleware对象,用于封装处理HTTP连接和请求的中间件

C#
public static IConnectionBuilder UseHttpServer<TContext>(this IConnectionBuilder builder, ServiceContext serviceContext, IHttpApplication<TContext> application, HttpProtocols protocols, bool addAltSvcHeader) where TContext : notnull
{
    var middleware = new HttpConnectionMiddleware<TContext>(serviceContext, application, protocols, addAltSvcHeader);
    return builder.Use(next =>
    {
        // 实际的请求处理
        return middleware.OnConnectionAsync;
    });
}

创建HttpConnection对象,并调用ProcessRequestsAsync处理传入的请求

C#
public Task OnConnectionAsync(ConnectionContext connectionContext)
{
    var httpConnectionContext = new HttpConnectionContext();

    var connection = new HttpConnection(httpConnectionContext);

    return connection.ProcessRequestsAsync(_application);
}

创建Http2Connection连接对象,并注册停止清理事件,调用ProcessRequestsAsync方法处理请求

C#
public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> httpApplication) where TContext : notnull
{
    IRequestProcessor? requestProcessor = new Http2Connection((HttpConnectionContext)_context);

    if (requestProcessor != null)
    {
        // 注册停止处理请求事件
        using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((HttpConnection)state!).StopProcessingNextRequest(), this);

        // 注册执行清理操作事件
        using var closedRegistration = _context.ConnectionContext.ConnectionClosed.Register(state => ((HttpConnection)state!).OnConnectionClosed(), this);

        await requestProcessor.ProcessRequestsAsync(httpApplication);
    }
    
}

从ProcessRequestsAsync方法就进入核心解析环节了,该方法负责读取和解析传入的HTTP/2帧,并执行相应的操作来处理请求。为了保证性能和可靠性,该方法中还使用了心跳检测、流量控制和超时控制等技巧。

通过循环读取数据并使用 ProcessFrameAsync方法处理传入的HTTP/2帧,直到收到终止连接的帧或者出现错误。

C#
private Task ProcessFrameAsync<TContext>(IHttpApplication<TContext> application, in ReadOnlySequence<byte> payload) where TContext : notnull
{
    // 请求流标识符必须是奇数
    if (_incomingFrame.StreamId != 0 && (_incomingFrame.StreamId & 1) == 0)
    {
        throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdEven(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
    }
    
    // 根据帧类型分发到不同的处理方法中
    return _incomingFrame.Type switch
    {
        Http2FrameType.DATA => ProcessDataFrameAsync(payload),
        Http2FrameType.HEADERS => ProcessHeadersFrameAsync(application, payload),
        Http2FrameType.PRIORITY => ProcessPriorityFrameAsync(),
        Http2FrameType.RST_STREAM => ProcessRstStreamFrameAsync(),
        Http2FrameType.SETTINGS => ProcessSettingsFrameAsync(payload),
        Http2FrameType.PUSH_PROMISE => throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorPushPromiseReceived, Http2ErrorCode.PROTOCOL_ERROR),
        Http2FrameType.PING => ProcessPingFrameAsync(payload),
        Http2FrameType.GOAWAY => ProcessGoAwayFrameAsync(),
        Http2FrameType.WINDOW_UPDATE => ProcessWindowUpdateFrameAsync(),
        Http2FrameType.CONTINUATION => ProcessContinuationFrameAsync(payload),
        _ => ProcessUnknownFrameAsync(),
    };
}

读取ProcessHeadersFrameAsync头部数据时,如果是新的数据,就开启新的数据流

C#
private Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> application, in ReadOnlySequence<byte> payload) where TContext : notnull
{      
    // ......

    // 开始一个新的Stream
    _currentHeadersStream = GetStream(application);

    _headerFlags = _incomingFrame.HeadersFlags;
    
    // 荷载数据
    var headersPayload = payload.Slice(0, _incomingFrame.HeadersPayloadLength); 

    // 解析请求头部数据
    return DecodeHeadersAsync(_incomingFrame.HeadersEndHeaders, headersPayload);          
}

private Task DecodeHeadersAsync(bool endHeaders, in ReadOnlySequence<byte> payload)
{
    _highestOpenedStreamId = _currentHeadersStream.StreamId;
    
    // 解码数据
    _hpackDecoder.Decode(payload, endHeaders, handler: this);
    
    // 当头部信息解码完成,开启新的数据流并重置处理状态,迎接下一个请求
    if (endHeaders)
    {
        _currentHeadersStream.OnHeadersComplete();
        StartStream();
        ResetRequestHeaderParsingState();
    }

    return Task.CompletedTask;
}

Decode解码方法中使用HPACK算法和状态机算法对HTTP/2请求头部进行解码。本篇文章中就不继续深究了......

1148127-20230615182739092-28766553.png

StartStream方法用于处理 HTTP/2 的流开始,并进行一些相关的检查和操作,如添加到流字典、计数增加、验证标头等。在做了诸多校验工作后,进行执行。

C#
private void StartStream()
{
    // _scheduleInline 仅在测试中为 true
    if (!_scheduleInline)
    {
        // 不能让应用程序代码阻塞连接处理循环。
        ThreadPool.UnsafeQueueUserWorkItem(_currentHeadersStream, preferLocal: false);
    }
    else
    {
        _currentHeadersStream.Execute();
    }
}

Execute方法在处理请求之前进行一些日志记录和度量统计操作,并调用异步方法 ProcessRequestsAsync() 来处理请求

C#
public override void Execute()
{
    KestrelEventSource.Log.RequestQueuedStop(this, AspNetCore.Http.HttpProtocol.Http2);
    ServiceContext.Metrics.RequestQueuedStop(MetricsContext, AspNetCore.Http.HttpProtocol.Http2);

    // REVIEW: Should we store this in a field for easy debugging?
    _ = ProcessRequestsAsync(_application);
}

ProcessRequests是异步处理请求的方法。使用循环来处理多个请求,并在每个请求处理的不同阶段执行相应的操作,如解析请求、运行应用程序代码、发送响应等。同时,它还处理了各种异常情况,并记录日志。循环会一直执行,直到保持连接的标志 _keepAlive 被设置为 false 或需要结束连接。并在此处创建了HttpContext对象

C#
private async Task ProcessRequests<TContext>(IHttpApplication<TContext> application) where TContext : notnull
{
    while (_keepAlive)
    {
        BeginRequestProcessing();

        // 尝试解析请求,直到成功解析请求或者需要结束连接
        var result = default(ReadResult);
        bool endConnection;
        do
        {
            if (BeginRead(out var awaitable))
            {
                result = await awaitable;
            }
        } while (!TryParseRequest(result, out endConnection));


        if (endConnection)
        {
            // 连接已经结束,停止处理请求
            return;
        }

        // 创建消息体
        var messageBody = CreateMessageBody();
        if (!messageBody.RequestKeepAlive)
        {
            _keepAlive = false;
        }

        // 初始化请求体控制器
        InitializeBodyControl(messageBody);

        // 创建上下文对象
        var context = application.CreateContext(this);

        // 运行应用程序对该请求的处理代码
        await application.ProcessRequestAsync(context);

        // 方法停止请求体控制器
        await _bodyControl.StopAsync();

        // 释放上下文对象
        application.DisposeContext(context, _applicationException);

        // 回到 while 循环的开头,继续处理下一个请求

    }
}

该方法接受一个 IFeatureCollection 类型的参数,并返回一个 HttpContext 对象

C#
public HttpContext CreateContext(IFeatureCollection contextFeatures)
{
    return _httpContextFactory?.Create(contextFeatures) ?? new DefaultHttpContext(contextFeatures);
}

初始化 DefaultHttpContext 对象的 _features、_request 和 _response 成员变量,并创建与当前上下文相关联的默认的请求和响应对象

C#
public DefaultHttpContext(IFeatureCollection features)
{
    _features.Initalize(features);
    _request = new DefaultHttpRequest(this);
    _response = new DefaultHttpResponse(this);
}

通过本篇文章可以深入了解了HTTP请求的数据流转过程。了解了数据在客户端和服务器之间的流动方式,以及HTTP报文的结构。

此外,我们还对Kestrel进行了源码解读,并了解了如何创建和管理HttpContext。Kestrel作为高性能的Web服务器,扮演着连接客户端和应用程序的桥梁,而HttpContext则提供了对请求和响应的上下文信息和处理能力。

通过深入研究和理解HTTP请求的数据流转过程以及Kestrel和HttpContext的工作原理,我们可以清晰的认知到整个运作流程。当然还有很多细节没有表述,在以后遇见问题的时候,可以快速定位问题或者查阅相关模块代码。以及了解如何去定制想要的扩展功能。

由于我阅读时喜欢一次性阅读完整篇文章,因此我写文章时常常会花费很长时间,这也导致我的文章变得相对较长。我也会考虑你是否有足够的耐心和时间来阅读整篇文章,如果你有好写作技巧,请指教。总之,完成一篇长文后,我会感到非常舒适和满足,很有成就感!

如果您觉得这篇文章有所收获,还请点个赞并关注。如果您有宝贵建议,欢迎在评论区留言,非常感谢您的支持!

(也可以关注我的公众号噢:Broder,万分感谢_)

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK