和所有的服务器一样,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的更多相关文章

  1. ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程

    从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...

  2. 写一个迷你版Smarty模板引擎,对认识模板引擎原理非常好(附代码)

    前些时间在看创智博客韩顺平的Smarty模板引擎教程,再结合自己跟李炎恢第二季开发中CMS系统写的tpl模板引擎.今天就写一个迷你版的Smarty引擎,虽然说我并没有深入分析过Smarty的源码,但是 ...

  3. 人脸验证算法Joint Bayesian详解及实现(Python版)

    人脸验证算法Joint Bayesian详解及实现(Python版) Tags: JointBayesian DeepLearning Python 本博客仅为作者记录笔记之用,不免有很多细节不对之处 ...

  4. Cookie 详解以及实现一个 cookie 操作库

    Cookie 详解以及实现一个 cookie 操作库 cookie 在前端有着大量的应用,但有时我们对它还是一知半解.下面来看看它的一些具体的用法 Set-Cookie 服务器通过设置响应头来设置客户 ...

  5. 直播的本质(创业者应该要从商业模式的右边开始思考,你为用户创造了什么价值?找客户并不难,但要想办法让客户不离不弃;PC端功能的丰富很重要,因为手机版通常只是一个迷你版)

    我想稍微给直播这件事浇点冷水. 的确,直播现在越来越火,YouTube凭着良好的基础建设平台前段时间也做起了直播,Facebook Live最近也加入了变脸.预定直播时间和双人录制的功能,更不用说国内 ...

  6. Koa源码解析,带你实现一个迷你版的Koa

    前言 本文是我在阅读 Koa 源码后,并实现迷你版 Koa 的过程.如果你使用过 Koa 但不知道内部的原理,我想这篇文章应该能够帮助到你,实现一个迷你版的 Koa 不会很难. 本文会循序渐进的解析内 ...

  7. Net is as typeof 运行运算符详解 net 自定义泛型那点事

    Net is as typeof 运行运算符详解   概述 在了解运行运算符的前提我们需要了解什么是RTTI ,在任何一门面向对象的语言中,都有RTTI这个概念(即 运行时). RTTI(Run-Ti ...

  8. java中的注解详解和自定义注解

    一.java中的注解详解 1.什么是注解 用一个词就可以描述注解,那就是元数据,即一种描述数据的数据.所以,可以说注解就是源代码的元数据.比如,下面这段代码: @Override public Str ...

  9. KestrelServer详解[1]:注册监听终结点(Endpoint)

    具有跨平台能力的KestrelServer是最重要的服务器类型.针对KestrelServer的设置均体现在KestrelServerOptions配置选项上,注册的终结点是它承载的最重要的配置选项. ...

随机推荐

  1. java-poi 批量导入excel数据

    1,首先,前端发送MultipartFile类型文件,后端接收 2,分别创建多个ImportParams对象(easypoi),对应工作蒲 注意:pom中 要有相对应的配置 <!-- easyp ...

  2. 关于 ios 动画枚举翻译

    例子 + (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewA ...

  3. SpringCloud微服务实战——搭建企业级开发框架(三十八):搭建ELK日志采集与分析系统

      一套好的日志分析系统可以详细记录系统的运行情况,方便我们定位分析系统性能瓶颈.查找定位系统问题.上一篇说明了日志的多种业务场景以及日志记录的实现方式,那么日志记录下来,相关人员就需要对日志数据进行 ...

  4. Learning Schedules

    近期目标 1. 争取搞定小论文 2. Java SE 学习 3. 剑指Offer每日1-2题

  5. cs144 lab0 lab1记录

    这个叫什么?Write Up吗 lab0 lab0要做的事,实现buffer部分的功能,要自己写变量以及实现接口. 成员函数 write()向buffer内写数据,peek_out()  pop_ou ...

  6. python数据分析与挖掘实战第二版pdf-------详细代码与实现

    [书名]:PYTHON数据分析与挖掘实战 第2版[作者]:张良均,谭立云,刘名军,江建明著[出版社]:北京:机械工业出版社[时间]:2020[页数]:340[isbn]:9787111640028 学 ...

  7. VisualStudio2019 利用代码片段管理器新建快捷命令

    原文:https://www.cnblogs.com/huguodong/p/12694902.html 一.前言 VisualStudio 可以通过敲出缩写字符串,然后按两次Tab按键自动展开成一段 ...

  8. Hadoop2.7.2源码编译过程

    目录 准备工作 jar包安装 源码编译 准备工作 CentOS可以联网,验证:ping www.baidu.com 是畅通的 jar 包准备(hadoop 源码.JDK8.maven.ant .pro ...

  9. Pycharm:集体缩进、注释、折叠

    1.集体缩进 选中代码块,按TAB 2.集体前移 选中代码块,Shift TAB 3.集体注释 选中代码块,CTRL + / 4.取消集体注释 再按一下CTRL+/ 5.集体折叠 CTRL+SHIFT ...

  10. Excel VBA中写SQL,这些问题的方法你一定要牢记

    小爬之前的文章 [Excel VBA中写SQL,这些问题你一定为此头痛过]中详细讲诉了一些常见的VBA 中使用SQL遇到的问题,这里再补充两个常见的问题场景及对应的解决方案,希望你们看了后能够思路开阔 ...