.NET原始碼解讀kestrel伺服器及建立HttpContext物件流程

2023-06-16 12:01:01

.NET本身就是一個基於中介軟體(middleware)的框架,它通過一系列的中介軟體元件來處理HTTP請求和響應。因此,本篇文章主要描述從使用者鍵入請求到伺服器響應的大致流程,並深入探討.NET通過kestrel將HTTP報文轉換為HttpContext物件。

通過本文,您可以瞭解以下內容:

  • http的資料流轉流程
  • 原始碼解讀kestrel伺服器的運作流程及生成HttpContext物件

一、HTTP請求的資料流轉過程

1. 資料流轉

HTTP 請求的資料流轉過程非常複雜,涉及多個協定層次和網路裝置。通過資料流轉示意圖可以簡要了解該流程:

  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、乙太網頭部,然後在每個層次進行解析,最終抵達目標伺服器。

2. 報文資料格式

下邊貼一張網路包的報文資料格式圖:

想深入瞭解更多計算機網路知識的同學,可以自行查閱書籍和資料,這裡有位博主總結的很好,地址:小林coding

二、認識kestrel和HttpContext

1. kestrel的作用

Kestrel 是一個基於libuv的跨平臺Web 伺服器,是.NET中預設啟用的 Web 伺服器,可以處理來自使用者端的 HTTP 請求和響應。

圖一 內網存取程式

圖二 反向代理存取程式

2. 什麼是HttpContext?

HttpContext儲存有關 Http 請求的當前資訊。它包含授權,身份驗證,請求,響應,對談,專案,使用者,表單選項等資訊。收到每個 HTTP 請求時,HttpContext都會初始化一個包含當前資訊的新物件。

想要了解更多HttpContext物件的屬性和方法,請直接參閱官方檔案

3. .NET中如何存取HttpContext

  • 使用 .NET Core 內建依賴項注入容器註冊依賴項,如下所示的 Startup.cs設定服務類方法:
// 注入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方法建立一個預設的主機構建器,設定應用程式的預設設定以及注入基礎服務。

// 在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擴充套件方法設定所需的選項,最終建立主機和服務容器範例

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構建預設主機

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協定實現高效資料傳輸的方式

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伺服器執行狀況的資料
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方法。

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主機,並進行啟動

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的如果您感興趣,請自行查閱原始碼。

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方法,先看下啟動監聽的方法。

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物件,並將其傳遞給連線處理委託進行相應的操作

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迴圈不斷監聽連線請求

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執行方法就是呼叫了我們上文中,先跳過的委託部分

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

回到上文中的UseHttpServer方法,該方法中建立HttpConnectionMiddleware物件,用於封裝處理HTTP連線和請求的中介軟體

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處理傳入的請求

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

    var connection = new HttpConnection(httpConnectionContext);

    return connection.ProcessRequestsAsync(_application);
}

建立Http2Connection連線物件,並註冊停止清理事件,呼叫ProcessRequestsAsync方法處理請求

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幀,直到收到終止連線的幀或者出現錯誤。

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頭部資料時,如果是新的資料,就開啟新的資料流

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請求頭部進行解碼。本篇文章中就不繼續深究了......

StartStream方法用於處理 HTTP/2 的流開始,並進行一些相關的檢查和操作,如新增到流字典、計數增加、驗證檔頭等。在做了諸多校驗工作後,進行執行。

private void StartStream()
{
    // _scheduleInline 僅在測試中為 true
    if (!_scheduleInline)
    {
        // 不能讓應用程式程式碼阻塞連線處理迴圈。
        ThreadPool.UnsafeQueueUserWorkItem(_currentHeadersStream, preferLocal: false);
    }
    else
    {
        _currentHeadersStream.Execute();
    }
}

Execute方法在處理請求之前進行一些紀錄檔記錄和度量統計操作,並呼叫非同步方法 ProcessRequestsAsync() 來處理請求

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物件

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 物件

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

初始化 DefaultHttpContext 物件的 _features、_request 和 _response 成員變數,並建立與當前上下文相關聯的預設的請求和響應物件

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,萬分感謝_)