KestrelServer详解[3]: 自定义一个迷你版的KestrelServer
和所有的服务器一样,KestrelServer最终需要解决的是网络传输的问题。在《网络连接的创建》,我们介绍了KestrelServer如何利用连接接听器的建立网络连接,并再次基础上演示了如何直接利用建立的连接接收请求和回复响应。本篇更进一步,我们根据其总体设计,定义了迷你版的KestrelServer让读者看看这个重要的服务器大体是如何实现的。本文提供的示例演示已经同步到《ASP.NET Core 6框架揭秘-实例演示版》)
一、ConnectionDelegate
二、IConnectionBuilder
三、HTTP 1.x/HTTP 2.x V.S. HTTP 3
四、MiniKestrelServer
一、ConnectionDelegate
ASP.NET CORE在“应用”层将针对请求的处理抽象成由中间件构建的管道,实际上KestrelServer面向“传输”层的连接也采用了这样的设计。当代表连接的ConnectionContext上下文创建出来之后,后续的处理将交给由连接中间件构建的管道进行处理。我们可以根据需要注册任意的中间件来处理连接,比如可以将并发连结的控制实现在专门的连接中间件中。ASP.NET CORE管道利用RequestDelegate委托来表示请求处理器,连接管道同样定义了如下这个ConnectionDelegate委托。
- public delegate Task ConnectionDelegate(ConnectionContext connection);
二、IConnectionBuilder
ASP.NET CORE管道中的中间件体现为一个Func<RequestDelegate, RequestDelegate>委托,连接管道的中间件同样可以利用Func<ConnectionDelegate, ConnectionDelegate>委托来表示。ASP.NET CORE管道中的中间件注册到IApplicationBuilder对象上并利用它将管道构建出来。连接管道依然具有如下这个IConnectionBuilder接口,ConnectionBuilder实现了该接口。
- public interface IConnectionBuilder
- {
- IServiceProvider ApplicationServices { get; }
- IConnectionBuilder Use(Func<ConnectionDelegate, ConnectionDelegate> middleware);
- ConnectionDelegate Build();
- }
- public class ConnectionBuilder : IConnectionBuilder
- {
- public IServiceProvider ApplicationServices { get; }
- public ConnectionDelegate Build();
- public IConnectionBuilder Use(Func<ConnectionDelegate, ConnectionDelegate> middleware);
- }
IConnectionBuilder接口还定义了如下三个扩展方法来注册连接中间件。第一个Use方法使用Func<ConnectionContext, Func<Task>, Task>委托来表示中间件。其余两个方法用来注册管道末端的中间件,这样的中间件本质上就是一个ConnectionDelegate委托,我们可以将其定义成一个派生于ConnectionHandler的类型。
- public static class ConnectionBuilderExtensions
- {
- public static IConnectionBuilder Use(this IConnectionBuilder connectionBuilder,Func<ConnectionContext, Func<Task>, Task> middleware);
- public static IConnectionBuilder Run(this IConnectionBuilder connectionBuilder,Func<ConnectionContext, Task> middleware);
- public static IConnectionBuilder UseConnectionHandler<TConnectionHandler>(this IConnectionBuilder connectionBuilder) where TConnectionHandler : ConnectionHandler;
- }
- public abstract class ConnectionHandler
- {
- public abstract Task OnConnectedAsync(ConnectionContext connection);
- }
三、HTTP 1.x/HTTP 2.x V.S. HTTP 3
KestrelServer针对HTTP 1.X/2和HTTP 3的设计和实现基本上独立的,这一点从监听器的定义就可以看出来。就连接管道来说,基于HTTP 3的多路复用连接通过MultiplexedConnectionContext表示,它也具有“配套”的MultiplexedConnectionDelegate委托和IMultiplexedConnectionBuilder接口。ListenOptions类型同时实现了IConnectionBuilder和IMultiplexedConnectionBuilder接口,意味着我们在注册终结点的时候还可以注册任意中间件。
- public delegate Task MultiplexedConnectionDelegate(MultiplexedConnectionContext connection);
- public interface IMultiplexedConnectionBuilder
- {
- IServiceProvider ApplicationServices { get; }
- IMultiplexedConnectionBuilder Use(Func<MultiplexedConnectionDelegate, MultiplexedConnectionDelegate> middleware);
- MultiplexedConnectionDelegate Build();
- }
- public class MultiplexedConnectionBuilder : IMultiplexedConnectionBuilder
- {
- public IServiceProvider ApplicationServices { get; }
- public IMultiplexedConnectionBuilder Use(Func<MultiplexedConnectionDelegate, MultiplexedConnectionDelegate> middleware);
- public MultiplexedConnectionDelegate Build();
- }
- public class ListenOptions : IConnectionBuilder, IMultiplexedConnectionBuilder
四、MiniKestrelServer
在了解了KestrelServer的连接管道后,我们来简单模拟一下这种服务器类型的实现,为此我们定义了一个名为MiniKestrelServer的服务器类型。简单起见,MiniKestrelServer只提供针对HTTP 1.1的支持。对于任何一个服务来说,它需要将请求交付给一个IHttpApplication<TContext>对象进行处理,MiniKestrelServer将这项工作实现在如下这个HostedApplication<TContext>类型中。
- public class HostedApplication<TContext> : ConnectionHandler where TContext : notnull
- {
- private readonly IHttpApplication<TContext> _application;
- public HostedApplication(IHttpApplication<TContext> application) => _application = application;
- public override async Task OnConnectedAsync(ConnectionContext connection)
- {
- var reader = connection!.Transport.Input;
- while (true)
- {
- var result = await reader.ReadAsync();
- using (var body = new MemoryStream())
- {
- var (features, request, response) = CreateFeatures(result, body);
- var closeConnection = request.Headers.TryGetValue("Connection", out var vallue) && vallue == "Close";
- reader.AdvanceTo(result.Buffer.End);
- var context = _application.CreateContext(features);
- Exception? exception = null;
- try
- {
- await _application.ProcessRequestAsync(context);
- await ApplyResponseAsync(connection, response, body);
- }
- catch (Exception ex)
- {
- exception = ex;
- }
- finally
- {
- _application.DisposeContext(context, exception);
- }
- if (closeConnection)
- {
- await connection.DisposeAsync();
- return;
- }
- }
- if (result.IsCompleted)
- {
- break;
- }
- }
- static (IFeatureCollection, IHttpRequestFeature, IHttpResponseFeature) CreateFeatures(ReadResult result, Stream body)
- {
- var handler = new HttpParserHandler();
- var parserHandler = new HttpParser(handler);
- var length = (int)result.Buffer.Length;
- var array = ArrayPool<byte>.Shared.Rent(length);
- try
- {
- result.Buffer.CopyTo(array);
- parserHandler.Execute(new ArraySegment<byte>(array, 0, length));
- }
- finally
- {
- ArrayPool<byte>.Shared.Return(array);
- }
- var bodyFeature = new StreamBodyFeature(body);
- var features = new FeatureCollection();
- var responseFeature = new HttpResponseFeature();
- features.Set<IHttpRequestFeature>(handler.Request);
- features.Set<IHttpResponseFeature>(responseFeature);
- features.Set<IHttpResponseBodyFeature>(bodyFeature);
- return (features, handler.Request, responseFeature);
- }
- static async Task ApplyResponseAsync(ConnectionContext connection, IHttpResponseFeature response, Stream body)
- {
- var builder = new StringBuilder();
- builder.AppendLine($"HTTP/1.1 {response.StatusCode} {response.ReasonPhrase}");
- foreach (var kv in response.Headers)
- {
- builder.AppendLine($"{kv.Key}: {kv.Value}");
- }
- builder.AppendLine($"Content-Length: {body.Length}");
- builder.AppendLine();
- var bytes = Encoding.UTF8.GetBytes(builder.ToString());
- var writer = connection.Transport.Output;
- await writer.WriteAsync(bytes);
- body.Position = 0;
- await body.CopyToAsync(writer);
- }
- }
- }
HostedApplication<TContext>是对一个IHttpApplication<TContext>对象的封装。它派生于抽象类ConnectionHandler,重写的OnConnectedAsync方法将针对请求的读取和处理置于一个无限循环中。为了将读取的请求转交给IHostedApplication<TContext>对象进行处理,它需要根据特性集合将TContext上下文创建出来。这里提供的特性集合只包含三种核心的特性,一个是描述请求的HttpRequestFeature特性,它是利用HttpParser解析请求荷载内容得到的。另一个是描述响应的HttpResponseFeature特性,至于提供响应主体的特性由如下所示的StreamBodyFeature对象来表示。这三个特性的创建实现在CreateFeatures方法中。
- public class StreamBodyFeature : IHttpResponseBodyFeature
- {
- public Stream Stream { get; }
- public PipeWriter Writer { get; }
- public StreamBodyFeature(Stream stream)
- {
- Stream = stream;
- Writer = PipeWriter.Create(Stream);
- }
- public Task CompleteAsync() => Task.CompletedTask;
- public void DisableBuffering() { }
- public Task SendFileAsync(string path, long offset, long? count,
- CancellationToken cancellationToken = default)=> throw new NotImplementedException();
- public Task StartAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;
- }
包含三大特性的集合随后作为参数调用了IHostedApplication<TContext>对象的CreateContext方法将TContext上下文创建出来,此上下文作为参数传入了同一对象的ProcessRequestAsync方法,此时中间件管道接管请求。待中间件管道完成处理后, ApplyResponseAsync方法被调用以完成最终的响应工作。ApplyResponseAsync方法将响应状态从HttpResponseFeature特性中提取并生成首行响应内容(“HTTP/1.1 {StatusCode} {ReasonPhrase}”),然后再从这个特性中将响应报头提取出来并生成相应的文本。响应报文的首行内容和报头文本按照UTF-8编码生成二进制数组后利用ConnectionContext上下文的Transport属性返回的IDuplexPipe对象发送出去后,它再将StreamBodyFeature特性收集到的响应主体输出流“拷贝”到这个IDuplexPipe对象中,进而完成了针对响应主体内容的输出。
如下所示的是MiniKestrelServer类型的完整定义。该类型的构造函数中注入了用于提供配置选项的IOptions<KestrelServerOptions>特性和IConnectionListenerFactory工厂,并且创建了一个ServerAddressesFeature对象并注册到Features属性返回的特性集合中。
- public class MiniKestrelServer : IServer
- {
- private readonly KestrelServerOptions _options;
- private readonly IConnectionListenerFactory _factory;
- private readonly List<IConnectionListener> _listeners = new();
- public IFeatureCollection Features { get; } = new FeatureCollection();
- public MiniKestrelServer(IOptions<KestrelServerOptions> optionsAccessor, IConnectionListenerFactory factory)
- {
- _factory = factory;
- _options = optionsAccessor.Value;
- Features.Set<IServerAddressesFeature>(new ServerAddressesFeature());
- }
- public void Dispose() => StopAsync(CancellationToken.None).GetAwaiter().GetResult();
- public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) where TContext : notnull
- {
- var feature = Features.Get<IServerAddressesFeature>()!;
- IEnumerable<ListenOptions> listenOptions;
- if (feature.PreferHostingUrls)
- {
- listenOptions = BuildListenOptions(feature);
- }
- else
- {
- listenOptions = _options.GetListenOptions();
- if (!listenOptions.Any())
- {
- listenOptions = BuildListenOptions(feature);
- }
- }
- foreach (var options in listenOptions)
- {
- _ = StartAsync(options);
- }
- return Task.CompletedTask;
- async Task StartAsync(ListenOptions litenOptions)
- {
- var listener = await _factory.BindAsync(litenOptions.EndPoint,cancellationToken);
- _listeners.Add(listener!);
- var hostedApplication = new HostedApplication<TContext>(application);
- var pipeline = litenOptions.Use(next => context => hostedApplication.OnConnectedAsync(context)).Build();
- while (true)
- {
- var connection = await listener.AcceptAsync();
- if (connection != null)
- {
- _ = pipeline(connection);
- }
- }
- }
- IEnumerable<ListenOptions> BuildListenOptions(IServerAddressesFeature feature)
- {
- var options = new KestrelServerOptions();
- foreach (var address in feature.Addresses)
- {
- var url = new Uri(address);
- if (string.Compare("localhost", url.Host, true) == 0)
- {
- options.ListenLocalhost(url.Port);
- }
- else
- {
- options.Listen(IPAddress.Parse(url.Host), url.Port);
- }
- }
- return options.GetListenOptions();
- }
- }
- public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(_listeners.Select(it => it.DisposeAsync().AsTask()));
- }
如上所示的演示程序将替换了针对IServer的服务注册,意味着默认的KestrelServer将被替换成自定义的MiniKestrelServer。启动该程序后,由浏览器发送的HTTP请求(不支持HTTPS)同样会被正常处理,并得到如图18-6所示的响应内容。需要强调一下,MiniKestrelServer仅仅用来模拟KestrelServer的实现原理,不要觉得真实的实现会如此简单。
图1 由MiniKestrelServer回复的响应内容
KestrelServer详解[3]: 自定义一个迷你版的KestrelServer的更多相关文章
- ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程
从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...
- 写一个迷你版Smarty模板引擎,对认识模板引擎原理非常好(附代码)
前些时间在看创智博客韩顺平的Smarty模板引擎教程,再结合自己跟李炎恢第二季开发中CMS系统写的tpl模板引擎.今天就写一个迷你版的Smarty引擎,虽然说我并没有深入分析过Smarty的源码,但是 ...
- 人脸验证算法Joint Bayesian详解及实现(Python版)
人脸验证算法Joint Bayesian详解及实现(Python版) Tags: JointBayesian DeepLearning Python 本博客仅为作者记录笔记之用,不免有很多细节不对之处 ...
- Cookie 详解以及实现一个 cookie 操作库
Cookie 详解以及实现一个 cookie 操作库 cookie 在前端有着大量的应用,但有时我们对它还是一知半解.下面来看看它的一些具体的用法 Set-Cookie 服务器通过设置响应头来设置客户 ...
- 直播的本质(创业者应该要从商业模式的右边开始思考,你为用户创造了什么价值?找客户并不难,但要想办法让客户不离不弃;PC端功能的丰富很重要,因为手机版通常只是一个迷你版)
我想稍微给直播这件事浇点冷水. 的确,直播现在越来越火,YouTube凭着良好的基础建设平台前段时间也做起了直播,Facebook Live最近也加入了变脸.预定直播时间和双人录制的功能,更不用说国内 ...
- Koa源码解析,带你实现一个迷你版的Koa
前言 本文是我在阅读 Koa 源码后,并实现迷你版 Koa 的过程.如果你使用过 Koa 但不知道内部的原理,我想这篇文章应该能够帮助到你,实现一个迷你版的 Koa 不会很难. 本文会循序渐进的解析内 ...
- Net is as typeof 运行运算符详解 net 自定义泛型那点事
Net is as typeof 运行运算符详解 概述 在了解运行运算符的前提我们需要了解什么是RTTI ,在任何一门面向对象的语言中,都有RTTI这个概念(即 运行时). RTTI(Run-Ti ...
- java中的注解详解和自定义注解
一.java中的注解详解 1.什么是注解 用一个词就可以描述注解,那就是元数据,即一种描述数据的数据.所以,可以说注解就是源代码的元数据.比如,下面这段代码: @Override public Str ...
- KestrelServer详解[1]:注册监听终结点(Endpoint)
具有跨平台能力的KestrelServer是最重要的服务器类型.针对KestrelServer的设置均体现在KestrelServerOptions配置选项上,注册的终结点是它承载的最重要的配置选项. ...
随机推荐
- [入门到吐槽系列] Webix 10分钟入门 二 表单Form的使用
前言 继续接着上一篇的webix入门:https://www.cnblogs.com/zc22/p/15912342.html.今天完成剩下两个最重要的控件,表单和表格的使用.掌握了这两个,整个Web ...
- GAN实战笔记——第四章深度卷积生成对抗网络(DCGAN)
深度卷积生成对抗网络(DCGAN) 我们在第3章实现了一个GAN,其生成器和判别器是具有单个隐藏层的简单前馈神经网络.尽管很简单,但GAN的生成器充分训练后得到的手写数字图像的真实性有些还是很具说服力 ...
- kali系统语言设置
一.背景信息在安装完 kali linux 2020.1 时,其操作系统默认语言为英文的,我们操作起来比较麻烦,为了以后操作方便起见,这边将其操作系统默认语言更改为中文.本篇文章将带领各位小伙伴们一起 ...
- HTTP攻击与防范-命令注入攻击
实验目的 1.了解命令注入攻击攻击带来的危险性. 2.掌握命令注入攻击攻击的原理与方法 3.掌握防范攻击的方法 实验原理 1.了解命令注入攻击攻击攻击带来的危险性. 2.掌握命令注入攻击攻击攻击的原理 ...
- PyTorch 如何理解张量:一维张量、二维张量、行/列向量、矩阵
理解张量,并将张量与线性代数的知识连接起来,我认为最重要的是理解 tensor 的两个属性:shape 和 ndim . ndim 表示张量的维度,一维张量的 ndim 值为 1,二维张量的 ndim ...
- 基于Redis分布式BitMap的应用
一.序言 在实际开发中常常遇到如下需求:判断当前元素是否存在于已知的集合中,将已知集合中的元素维护一个HashSet,使用时只需耗时O(1)的时间复杂度便可判断出结果,Java内部或者Redis均提供 ...
- linux-noshell的模式
转至:https://blog.csdn.net/ifubing/article/details/95509981 noshell 创建两个用户,一个直接创建,一切按默认的来 另一个创建时指定一下no ...
- 2020.9.28 多进程multiprocess 进程池pool 子进程subprocess 进程间通信
1.multiprocessing模块--跨平台版本的多进程模块 multiprocessing模块提供了一个Process类来代表一个进程对象,下面的例子演示了启动一个子进程并等待其结束: from ...
- c# 自定义控件-提示框(弹框)
分带取消按钮和不带取消按钮的 调用方法: frmMessageBox frm = new frmMessageBox("提示", "数据连接失败,请重试!", ...
- web -- ssrf
web学习之SSRF 最开始玩ctf接触的就是web但是随着开始逐渐专注于pwn,在加之web的知识体系本来就繁杂,所以准备写一期关于web大体的知识 day1 SSRF SSRF(Server-Si ...