目录

  • 目标
  • 源码

目标

理解 KestrelServer 如何接收网络请求,网络请求如何转换成 http request context(C# 可识别)

源码

https://github.com/dotnet/aspnetcore/

在目录 aspnetcore\src\Servers\Kestrel\Core\src\Internal 下有一个 KestrelServerImpl

internal class KestrelServerImpl : IServer

在 host 启动的时候调用了 server 的 startup 方法,可以从这个入口开始

public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) where TContext : notnull

StartAsync 方法主要分为以下三步

async Task OnBind(ListenOptions options, CancellationToken onBindCancellationToken)
{
...
} AddressBindContext = new AddressBindContext(_serverAddresses, Options, Trace, OnBind); await BindAsync(cancellationToken).ConfigureAwait(false);

BindAsync 方法中使用 AddressBindContext 进行绑定

await AddressBinder.BindAsync(Options.ListenOptions, AddressBindContext!, cancellationToken).ConfigureAwait(false);

在 AddressBinder 的 BindAsync 方法中创建了多种策略进行绑定

var strategy = CreateStrategy(
listenOptions.ToArray(),
context.Addresses.ToArray(),
context.ServerAddressesFeature.PreferHostingUrls); ... await strategy.BindAsync(context, cancellationToken).ConfigureAwait(false);

例如 AddressesStrategy,它有自己的一个绑定方法

private class AddressesStrategy : IStrategy
{
protected readonly IReadOnlyCollection<string> _addresses; public AddressesStrategy(IReadOnlyCollection<string> addresses)
{
_addresses = addresses;
} public virtual async Task BindAsync(AddressBindContext context, CancellationToken cancellationToken)
{
foreach (var address in _addresses)
{
var options = ParseAddress(address, out var https);
context.ServerOptions.ApplyEndpointDefaults(options); if (https && !options.IsTls)
{
options.UseHttps();
} await options.BindAsync(context, cancellationToken).ConfigureAwait(false);
}
}
}

options 来自 IConnectionBuilder 的 ListenOptions 的绑定

public class ListenOptions : IConnectionBuilder, IMultiplexedConnectionBuilder

这一路走下来发现找不到重点,所以需要换一个方向从 OnBind 方法入手,它是一个委托,需要找到调用的地方

async Task OnBind(ListenOptions options, CancellationToken onBindCancellationToken)

可以看到 OnBind 方法传入到 AddressBindContext 中

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

在 AddressBindContext 中它是一个 CreateBinding

public Func<ListenOptions, CancellationToken, Task> CreateBinding { get; }

全局搜索 CreateBinding

可以找到在 AddressBinder 的 BindEndpointAsync 方法中被调用

internal static async Task BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken)
{
try
{
await context.CreateBinding(endpoint, cancellationToken).ConfigureAwait(false);
}
catch (AddressInUseException ex)
{
throw new IOException(CoreStrings.FormatEndpointAlreadyInUse(endpoint), ex);
} context.ServerOptions.OptionsInUse.Add(endpoint);
}

而 BindEndpointAsync 方法被 ListenOptions 的 BindAsync 方法调用,也就是上面提到的 StartAsync 的第三步 BindAsync 走到的 ListenOptions

internal virtual async Task BindAsync(AddressBindContext context, CancellationToken cancellationToken)
{
await AddressBinder.BindEndpointAsync(this, context, cancellationToken).ConfigureAwait(false);
context.Addresses.Add(GetDisplayName());
}

在第三步 BindAsync 方法中加载配置,加载之后才调用真正的绑定方法

Options.ConfigurationLoader?.Load();

await AddressBinder.BindAsync(Options.ListenOptions, AddressBindContext!, cancellationToken).ConfigureAwait(false);

所以整个过程的重点在第一步的 OnBind,而 OnBind 的重点在于 TransportManager 的 BindAsync 方法

options.EndPoint = await _transportManager.BindAsync(options.EndPoint, multiplexedConnectionDelegate, options, onBindCancellationToken).ConfigureAwait(false);

进入 TransportManager 中可以看到在 BindAsync 方法中开始接收

StartAcceptLoop(new GenericConnectionListener(transport), c => connectionDelegate(c), endpointConfig);

在 StartAcceptLoop 方法中调用了 StartAcceptingConnections 方法

var acceptLoopTask = connectionDispatcher.StartAcceptingConnections(connectionListener);

在 StartAcceptingConnections 方法中将需要被执行的方法添加到队列中

ThreadPool.UnsafeQueueUserWorkItem(StartAcceptingConnectionsCore, listener, preferLocal: false);

在 StartAcceptingConnectionsCore 里面开始监听接收,这就是关键,这里执行了 kestrelConnection,而 kestrelConnection 又包含 _connectionDelegate

var connection = await listener.AcceptAsync();

var kestrelConnection = new KestrelConnection<T>(
id, _serviceContext, _transportConnectionManager, _connectionDelegate, connection, Log); _transportConnectionManager.AddConnection(id, kestrelConnection); ThreadPool.UnsafeQueueUserWorkItem(kestrelConnection, preferLocal: false);

在 kestrelConnection 中可以看到整个 ExecuteAsync 方法里面只执行了 _connectionDelegate

await _connectionDelegate(connectionContext);

意识到 _connectionDelegate 的重要性之后再往回找是怎么传进来的,可以找到是在 KestrelServerImpl 中通过 ListenOptions 构建出来的

var connectionDelegate = options.Build();

在 Build 方法里面可以看到它是一个管道

ConnectionDelegate app = context =>
{
return Task.CompletedTask;
}; for (var i = _middleware.Count - 1; i >= 0; i--)
{
var component = _middleware[i];
app = component(app);
} return app;

通过 _middleware 的 Use 方法的引用找不到有价值的信息

public IConnectionBuilder Use(Func<ConnectionDelegate, ConnectionDelegate> middleware)
{
_middleware.Add(middleware);
return this;
}

于是回到 KestrelServerImpl 中,查看 UseHttpServer 方法

options.UseHttpServer(ServiceContext, application, options.Protocols, addAltSvcHeader);

可以看到这个方法构建了一个 HttpConnectionMiddleware

var middleware = new HttpConnectionMiddleware<TContext>(serviceContext, application, protocols, addAltSvcHeader);
return builder.Use(next =>
{
return middleware.OnConnectionAsync;
});

进入 HttpConnectionMiddleware 可以看到一个核心方法 OnConnectionAsync,创建了一个 HttpConnection,然后调用 ProcessRequestsAsync

var connection = new HttpConnection(httpConnectionContext);

return connection.ProcessRequestsAsync(_application);

在 ProcessRequestsAsync 方法中可以看到 KestrelServer 的核心逻辑,根据不同的协议,执行不同的逻辑;同时可以看到它是如何处理请求的,通过 requestProcessor 处理请求

switch (SelectProtocol())
{
case HttpProtocols.Http1:
requestProcessor = _http1Connection = new Http1Connection<TContext>((HttpConnectionContext)_context);
_protocolSelectionState = ProtocolSelectionState.Selected;
break;
case HttpProtocols.Http2:
requestProcessor = new Http2Connection((HttpConnectionContext)_context);
_protocolSelectionState = ProtocolSelectionState.Selected;
break;
case HttpProtocols.Http3:
requestProcessor = new Http3Connection((HttpMultiplexedConnectionContext)_context);
_protocolSelectionState = ProtocolSelectionState.Selected;
break;
} await requestProcessor.ProcessRequestsAsync(httpApplication);

requestProcessor 是一个 IRequestProcessor 接口,它有多个实现,以 Http2Connection 为例

internal partial class Http2Connection : IHttp2StreamLifetimeHandler, IHttpStreamHeadersHandler, IRequestProcessor

在 Http2Connection 的 ProcessRequestsAsync 方法中读取流,解析转换,处理

await _frameWriter.WriteWindowUpdateAsync(0, diff);

var result = await Input.ReadAsync();

await ProcessFrameAsync(application, framePayload);

当 HttpConnectionMiddleware 的 OnConnectionAsync 处理完之后,如何与应用层代码拼接,这里只是 Kestrel 的处理

可以通过 IRequestProcessor 接口的 ProcessRequestsAsync 方法的实现找到 HttpProtocol 的 ProcessRequestsAsync 方法,可以看到它执行了一个 ProcessRequests 方法

await ProcessRequests(application);

在 ProcessRequests 方法中将从 Body 里面获取的内容封装到一个 context,这个才是真正的 HttpContext,然后再运行应用层代码,之前都是 Kestrel 的解析逻辑,这里才是串联到我们构建的管道 application

InitializeBodyControl(messageBody);

var context = application.CreateContext(this);

// Run the application code for this request
await application.ProcessRequestAsync(context);

接下来看一下 application 是如何传过来的,一直找到 HttpConnection,HttpConnectionMiddleware,HttpConnectionBuilderExtensions,KestrelServerImpl

public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) where TContext : notnull

可以看到是 Host 调用 Server 的 StartAsync 传进来的,这里体现了职责分离的原则,对于应用层的管道,定义了一个 IHttpApplication application,这就是 requestDelegate

从 Host 传到 Server,Server 完成了网络端口的绑定,网络的监听接收,网络二进制转换成具体的 c# 可识别的 HTTPContext 之后,调用了 Host 那边封装好的一个 application 应用层的管道,这是 Host 在 Startup 里面定义的,这就是一个完整的过程

文档:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/?view=aspnetcore-6.0&tabs=windows#kestrel

课程链接

https://appsqsyiqlk5791.h5.xiaoeknow.com/v1/course/video/v_5f39bdb8e4b01187873136cf?type=2

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

如有任何疑问,请与我联系 (MingsonZheng@outlook.com) 。

.NET 云原生架构师训练营(KestrelServer源码分析)--学习笔记的更多相关文章

  1. .NET 云原生架构师训练营(组合模式)--学习笔记

    目录 引入 组合模式 源码 引入 在上一篇执行 _connectionDelegate 之后,HttpConnectionMiddleware 处理请求 return connection.Proce ...

  2. .NET 云原生架构师训练营(模板方法 && 建造者)--学习笔记

    目录 模板方法 源码 建造者 模板方法 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤 源码 https://github.com ...

  3. .NET 云原生架构师训练营(Identity Server)--学习笔记

    目录 OAuth 2.0 OpenID Connect QuickStart OAuth 2.0 概念 过程 通信 组件 示例代码 概念 OAuth 2.0 是一个授权协议,它允许软件应用代表(而不是 ...

  4. .NET 云原生架构师训练营(模块一 架构师与云原生)--学习笔记

    目录 什么是软件架构 软件架构的基本思路 单体向分布式演进.云原生.技术中台 1.1 什么是软件架构 1.1.1 什么是架构? Software architecture = {Elements, F ...

  5. .NET 云原生架构师训练营(建立系统观)--学习笔记

    目录 目标 ASP .NET Core 什么是系统 什么是系统思维 系统分解 什么是复杂系统 作业 目标 通过整体定义去认识系统 通过分解去简化对系统的认识 ASP .NET Core ASP .NE ...

  6. .NET 云原生架构师训练营(权限系统 RGCA 架构设计)--学习笔记

    目录 项目核心内容 实战目标 RGCA 四步架构法 项目核心内容 无代码埋点实现对所有 API Action 访问控制管理 对 EF Core 实体新增.删除.字段级读写控制管理 与 Identity ...

  7. .NET 云原生架构师训练营(模块二 基础巩固 EF Core 更新和迁移)--学习笔记

    2.4.6 EF Core -- 更新 状态 自动变更检测 不查询删除和更新 并发 状态 Entity State Property State Entity State Added 添加 Uncha ...

  8. .NET 云原生架构师训练营(模块二 基础巩固 MongoDB 问答系统)--学习笔记

    2.5.6 MongoDB -- 问答系统 MongoDB 数据库设计 API 实现概述 MongoDB 数据库设计 设计优化 内嵌(mongo)还是引用(mysql) 数据一致性 范式:将数据分散到 ...

  9. .NET 云原生架构师训练营(模块二 基础巩固 MongoDB API实现)--学习笔记

    2.5.7 MongoDB -- API实现 问题查询单个实现 问题查询列表实现 问题跨集合查询实现 问题创建实现 问题更新实现 问题回答实现 问题评论实现 问题投票实现 回答实现 QuestionC ...

随机推荐

  1. Uni-app原生插件入门使用教程「2」:如何离线使用原生插件

    当HBuilderX中提供的能力无法满足App功能需求,需要通过使用Andorid/iOS原生开发实现时,可使用App离线SDK开发原生插件来扩展原生能力. 如使用Uniapp开发直播功能时,需要调用 ...

  2. 【PS算法理论探讨二】 Photoshop中图层样式之 投影样式 算法原理初探讨。

    接下来几篇文章我们将稍微简单的探索下PS中多种图层混合模式的算法内部原理,因为毕竟没有这方面的官方资料,所以很多方面也只是本人自己的探索和实践,有可能和实际的情况有着较大的差异. 在PS的实践中,图层 ...

  3. VS2013 or up version +常用插件

    !!版权声明:本文为博主原创文章,版权归原文作者和博客园共有,谢绝任何形式的 转载!! 作者:mohist 下载地址: https://github.com/mohistH/vs2013_extens ...

  4. c++ 设计模式概述之策略

    代码写的不规范,目的是为了缩短文章篇幅,实际中请不要这样做. 1.概述 类比现实生活中的场景,比如,我需要一块8G内存条,我可以选择:A.去线下实体店买,B.线上购买,C.其他渠道. 再比如,吃饭餐具 ...

  5. C++ 获取函数耗时

    C++ 记录耗时 #include <sys/timeb.h> #include <stdio.h> long long getSystemTime() { struct ti ...

  6. 【LeetCode】20. Valid Parentheses 有效的括号

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 个人公众号:负雪明烛 本文关键词:有效,括号,括号匹配,栈,题解,leetcode, 力扣 ...

  7. 1226 - One Unit Machine

    1226 - One Unit Machine   PDF (English) Statistics Forum Time Limit: 2 second(s) Memory Limit: 32 MB ...

  8. python学习第一天:window安装python开发环境完整篇

    我是跟着廖雪峰老师的的博客来一步一步来进行学习和实践后记录下来的,讲的非常地详细,推荐大家一起学习https://www.liaoxuefeng.com/wiki/0014316089557264a6 ...

  9. 第九届河南理工大学算法程序设计大赛 正式赛L:最优规划(最小生成树)

    单测试点时限: 1.0 秒 内存限制: 512 MB 有很多城市之间已经建立了路径,但是有些城市之间没有路径联通.为了联通所有的城市,现在需要添加一些路径,为了节约,需要满足添加总路径是最短的. 输入 ...

  10. <数据结构>XDOJ316.多点测试的写法

    问题与解答 问题描述 有一棵无限大的完全二叉树,该二叉树自上而下.自左而右从1开始编号.从某一个结点到根结点(编号是1的结点)都有一条唯一的路径,比如从5到根结点的路径是(5, 2, 1),从4到根结 ...