和所有的服务器一样,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委托。
publicdelegate Task ConnectionDelegate(ConnectionContext connection);
二、IConnectionBuilder
ASP.NET CORE管道中的中间件体现为一个Func
publicinterface IConnectionBuilder { IServiceProvider ApplicationServices {get; } IConnectionBuilder Use(Funcmiddleware); ConnectionDelegate Build(); }publicclass ConnectionBuilder : IConnectionBuilder {public IServiceProvider ApplicationServices {get; }public ConnectionDelegate Build();public IConnectionBuilder Use(Func middleware); }
IConnectionBuilder接口还定义了如下三个扩展方法来注册连接中间件。第一个Use方法使用Func
publicstaticclass ConnectionBuilderExtensions {publicstatic IConnectionBuilder Use(this IConnectionBuilder connectionBuilder,Func, Task> middleware);publicstatic IConnectionBuilder Run(this IConnectionBuilder connectionBuilder,Func middleware);publicstatic IConnectionBuilder UseConnectionHandler (this IConnectionBuilder connectionBuilder) where TConnectionHandler : ConnectionHandler; }publicabstractclass ConnectionHandler {publicabstract 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接口,意味着我们在注册终结点的时候还可以注册任意中间件。
publicdelegate Task MultiplexedConnectionDelegate(MultiplexedConnectionContext connection);publicinterface IMultiplexedConnectionBuilder { IServiceProvider ApplicationServices {get; } IMultiplexedConnectionBuilder Use(Funcmiddleware); MultiplexedConnectionDelegate Build(); }publicclass MultiplexedConnectionBuilder : IMultiplexedConnectionBuilder {public IServiceProvider ApplicationServices {get; }public IMultiplexedConnectionBuilder Use(Func middleware);public MultiplexedConnectionDelegate Build(); }publicclass ListenOptions : IConnectionBuilder, IMultiplexedConnectionBuilder
四、MiniKestrelServer
在了解了KestrelServer的连接管道后,我们来简单模拟一下这种服务器类型的实现,为此我们定义了一个名为MiniKestrelServer的服务器类型。简单起见,MiniKestrelServer只提供针对HTTP 1.1的支持。对于任何一个服务来说,它需要将请求交付给一个IHttpApplication
publicclass HostedApplication: ConnectionHandler where TContext : notnull {privatereadonly IHttpApplication _application;public HostedApplication(IHttpApplication application) => _application = application;publicoverride 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 (handler.Request); features.Set (responseFeature); features.Set (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 kvin 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
publicclass 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;publicvoid DisableBuffering() { }public Task SendFileAsync(string path,long offset,long? count, CancellationToken cancellationToken =default)=>thrownew NotImplementedException();public Task StartAsync(CancellationToken cancellationToken =default) => Task.CompletedTask; }
包含三大特性的集合随后作为参数调用了IHostedApplication
如下所示的是MiniKestrelServer类型的完整定义。该类型的构造函数中注入了用于提供配置选项的IOptions
publicclass MiniKestrelServer : IServer {privatereadonly KestrelServerOptions _options;privatereadonly IConnectionListenerFactory _factory;privatereadonly List
_listeners =new();public IFeatureCollection Features {get; } =new FeatureCollection();public MiniKestrelServer(IOptions optionsAccessor, IConnectionListenerFactory factory) { _factory = factory; _options = optionsAccessor.Value; Features.Set (new ServerAddressesFeature()); }publicvoid Dispose() => StopAsync(CancellationToken.None).GetAwaiter().GetResult();public Task StartAsync (IHttpApplication application, CancellationToken cancellationToken) where TContext : notnull { var feature = Features.Get ()!; IEnumerable listenOptions;if (feature.PreferHostingUrls) { listenOptions = BuildListenOptions(feature); }else { listenOptions = _options.GetListenOptions();if (!listenOptions.Any()) { listenOptions = BuildListenOptions(feature); } }foreach (var optionsin 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
(application); var pipeline = litenOptions.Use(next => context => hostedApplication.OnConnectedAsync(context)).Build(); while (true) { var connection = await listener.AcceptAsync();if (connection !=null) { _ = pipeline(connection); } } } IEnumerable
BuildListenOptions(IServerAddressesFeature feature) { var options =new KestrelServerOptions();foreach (var addressin 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())); }
实现的StartAsync
publicstaticclass KestrelServerOptionsExtensions {publicstatic IEnumerableGetListenOptions(this KestrelServerOptions options) { var property =typeof(KestrelServerOptions).GetProperty("ListenOptions",BindingFlags.NonPublic | BindingFlags.Instance);return (IEnumerable )property!.GetValue(options)!; } }
对于每一个表示注册终结点的ListenOptions配置选项,StartAsync
using App;using Microsoft.AspNetCore.Hosting.Server;using Microsoft.Extensions.DependencyInjection.Extensions; var builder = WebApplication.CreateBuilder(); builder.WebHost.UseKestrel(kestrel => kestrel.ListenLocalhost(5000));builder.Services.Replace(ServiceDescriptor.Singleton()); var app = builder.Build(); app.Run(context => context.Response.WriteAsync("Hello World!")); app.Run();
如上所示的演示程序将替换了针对IServer的服务注册,意味着默认的KestrelServer将被替换成自定义的MiniKestrelServer。启动该程序后,由浏览器发送的HTTP请求(不支持HTTPS)同样会被正常处理,并得到如图18-6所示的响应内容。需要强调一下,MiniKestrelServer仅仅用来模拟KestrelServer的实现原理,不要觉得真实的实现会如此简单。
图1 由MiniKestrelServer回复的响应内容