KestrelServer詳解[3]: 自定義一個迷你版的KestrelServer

和所有的服務器一樣,KestrelServer最終需要解決的是網絡傳輸的問題。在《KestrelServer詳解[2]: 網絡連接是如何創建的?》,我們介紹了KestrelServer如何利用連接接聽器的建立網絡連接,并再次基礎上演示了如何直接利用建立的連接接收請求和回復響應。本篇更進一步,我們根據其總體設計,定義了迷你版的KestrelServer讓讀者看看這個重要的服務器大體是如何實現的。[本文節選《ASP.NET Core 6框架揭秘》第18章]

一、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();
}

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()));
}

實現的StartAsync<TContext>方法先將IServerAddressesFeature特性提取出來,并利用其PreferHostingUrls屬性決定應該使用直接注冊到KestrelOptions配置選項上的終結點還是使用注冊在該特定上的監聽地址。如果使用后者,注冊的監聽地址會利用BuildListenOptions方法轉換成對應的ListenOptions列表,否則直接從KestrelOptions對象的ListenOptions屬性提取所有的ListenOptions列表,由于這是一個內部屬性,不得不利用如下這個擴展方法以反射的方式獲取這個列表。

public?static?class?KestrelServerOptionsExtensions
{public?static?IEnumerable<ListenOptions>?GetListenOptions(this?KestrelServerOptions?options){var?property?=?typeof(KestrelServerOptions).GetProperty("ListenOptions",BindingFlags.NonPublic?|?BindingFlags.Instance);return?(IEnumerable<ListenOptions>)property!.GetValue(options)!;}
}

對于每一個表示注冊終結點的ListenOptions配置選項,StartAsync<TContext>方法利用IConnectionListenerFactory工廠將對應的IConnectionListener監聽器創建出來,并綁定到指定的終結點上監聽連接請求。表示連接的ConnectionContext上下文一旦被創建出來后,該方法便會利用構建的連接管道對它進行處理。在調用ListenOptions配置選項的Build方法構建連接管道前,StartAsync<TContext>方法將HostedApplication<TContext>對象創建出來并作為中間件進行了注冊。所以針對連接的處理將被這個HostedApplication<TContext>對象接管。

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<IServer,?MiniKestrelServer>());
var?app?=?builder.Build();
app.Run(context?=>?context.Response.WriteAsync("Hello?World!"));
app.Run();

如上所示的演示程序將替換了針對IServer的服務注冊,意味著默認的KestrelServer將被替換成自定義的MiniKestrelServer。啟動該程序后,由瀏覽器發送的HTTP請求(不支持HTTPS)同樣會被正常處理,并得到如圖1所示的響應內容。需要強調一下,MiniKestrelServer僅僅用來模擬KestrelServer的實現原理,不要覺得真實的實現會如此簡單。

fb2b7fadc1895bf738dd3b9158279ee2.jpeg
圖1 由MiniKestrelServer回復的響應內容

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/282452.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/282452.shtml
英文地址,請注明出處:http://en.pswp.cn/news/282452.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

c# 文件下載

這樣的下載方式 減少服務器的壓力&#xff0c; 還有一種省懶勁的方式&#xff1a;后端在iis上配置一個虛擬目錄&#xff0c;然后讓前端自己拼url地址下載&#xff0c; 這個東西是給后期其他工作人員埋坑&#xff0c;哈哈。 本帖原文轉自與 農碼一生轉載于:https://www.cnbl…

Redis -- 基礎操作 [2]

一 獲取redis當前數據庫符合條件鍵名 [keys pattern]二 設置string形式key-value [set key value]三 獲取存儲在指定 key 中字符串的子字符串 [GETRANGE KEY start end]四 刪除指定鍵值對 [del key]五 為給定key設置過期時間 [Expire KEY SECONDS]注: Expireat KEY TIMESTAMP 同…

Centos7作為VNCserver,本地使用VNCViewer連接

1.概念 VNC是一個遠程連接工具 VNC is used to display an X windows session running on another computer. Unlike a remote X connection, the xserver is running on the remote computer, not on your local workstation. Your workstation ( Linux or Windows ) is only …

SQL Server CONVERT() 日期轉換為新數據類型的 通用函數

http://www.w3school.com.cn/sql/func_convert.asp轉載于:https://www.cnblogs.com/renzhituteng/p/6665569.html

在URL中實現簡易的WebAPI驗簽

本文主要介紹一種與微信公眾平臺對接方式類似的&#xff0c;為 AspNetCore 提供的一種簡易的 WebAPI 簽名驗證中間件。本文相關源碼和案例已開源&#xff0c;地址&#xff1a;https://github.com/sangyuxiaowu/SignAuthorization原理說明簡易的 API url 簽名驗證中間件&#xf…

Redis -- Hash(哈希) [3]

Redis Hash 是一個string類型的field和value的 映射表 &#xff0c;hash特別適合用于存儲對象。 注 : Redis 中每個 hash 可以存儲 232 - 1 鍵值對&#xff08;40多億&#xff09;。 比如這樣:注:在此,首先推薦一款redis可視化工具 https://redisdesktop.com/download , 是非常…

HBuilder 打包流程

1.運行HBuilder---百度搜索HBuilder&#xff0c;官網下載安裝包&#xff0c;解壓&#xff0c;運行HBuilder.exe。注冊賬號&#xff0c;并登陸 2.新建app---在左邊右鍵&#xff0c;選擇新建APP&#xff0c;或者&#xff0c;點擊中間的新建app 3.在彈出的窗口&#xff0c;填入應用…

pandas所占內存釋放

df pd.read_csv(....) 要調用循環處理多個文件時&#xff0c;內存占用情況嚴重&#xff0c;如果互相之間不需要調用&#xff0c;可以直接del df 釋放內存

Python3——字典

Python 字典(Dictionary) 字典是另一種可變容器模型&#xff0c;且可存儲任意類型對象。 字典的每個鍵值(key>value)對用冒號(:)分割&#xff0c;每個對之間用逗號(,)分割&#xff0c;整個字典包括在花括號({})中 定義字典 d {} d {key1 : value1, key2 : value2 } d di…

科技以換皮為本:路遙工具箱 V4 版本發布

作為定位“開發輔助”的工具&#xff0c;我也一直在想如何讓工具更有效率。是更快的打開速度還是更豐富的功能&#xff1f;路遙工具箱 V3 版本的界面布局是偏 BS 后臺系統的風格&#xff1a;可折疊的樹形菜單用來拓寬用戶的操作區域&#xff0c;多標簽的功能布局讓軟件保持整潔…

myisam數據表根據frm文件恢復數據表

有時,我們重裝mysql時,可能忘記備份數據了, 只留下了之前的mysql下面的data文件夾里的數據, 這時我們應該如何去恢復數據表呢 如果直接將原來的data目錄導進現在的mysql,肯定是不行的,其實很簡單 我們常用的數據表結構有myisam和innodb,這兩種數據表恢復數據的方式是不一樣的,這…

本文主要總結關于mysql的優化(將會持續更新)

2019獨角獸企業重金招聘Python工程師標準>>> ON DUPLICATE KEY UPDATE 事件背景 在閱讀公司原來代碼的過程中&#xff0c;我發現了這樣一段代碼: $sql "INSERT INTO {$table} ({$fields}) VALUES " . $values; if (!empty($onDuplicate)) {$sql . ON DU…

CS Academy Gcd Rebuild

題目鏈接&#xff1a;https://csacademy.com/contest/archive/task/gcd-rebuild/statement/ 題目大意&#xff1a;給出一個N*M的矩陣&#xff0c;其中第i行j列表示gcd(a[i], b[j])&#xff0c;現在不知道數組a&#xff0c;b&#xff0c;給出這個矩陣&#xff0c;求a&#xff0c…

ASP.NET Core 在 IIS 下的兩種部署模式

KestrelServer最大的優勢體現在它的跨平臺的能力&#xff0c;如果ASP.NET CORE應用只需要部署在Windows環境下&#xff0c;IIS也是不錯的選擇。ASP.NET CORE應用針對IIS具有兩種部署模式&#xff0c;它們都依賴于一個IIS針對ASP.NET CORE Core的擴展模塊。一、ASP.NET CORE Cor…

navicat連接遠程mysql

環境介紹: 這里,我連接的是阿里云的服務器,自己搭的環境,用的是mysql 5.7一 首先第一步,需要進入遠程服務器的mysql,更改host訪問權限 然后,將root允許訪問的host 改為%(任何ip地址都可以訪問) 注: 原來是只允許本地訪問二 本地用navicat連接遠程mysql 1. 常規部分填寫2. SSH部…

2018-08-15期 HBase命令行使用案例

1、進入hbase命令行[roothadoop-server01 bin]# hbase shell2、命令行幫助COMMAND GROUPS:Group name: generalCommands: status, table_help, version, whoamiGroup name: ddlCommands: alter, alter_async, alter_status, create, describe, disable, disable_all, drop, dro…

面向對象五大設計原則

最近在看七牛云許式偉的架構課, 重溫了面向對象五大設計原則(SOLID)&#xff0c;扣理論文字找出處。&#xff08;當然許老板是不可能深聊這么低級的內容&#xff0c;&#x1f921;&#xff09;注意區分設計原則和設計模式。設計原則更為抽象和泛化&#xff1b;設計模式也是抽象…

python函數式編程-匿名函數

>>> map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]) [1, 4, 9, 16, 25, 36, 49, 64, 81] 關鍵字lambda表示匿名函數&#xff0c;冒號前面的x表示函數參數。 匿名函數有個限制&#xff0c;就是只能有一個表達式&#xff0c;不用寫return&#xff0c;返回值就是該表…

bean初始化、注銷

關于在spring 容器初始化 bean 和銷毀前所做的操作定義方式有三種&#xff1a; 第一種&#xff1a;通過PostConstruct 和 PreDestroy 方法 實現初始化和銷毀bean之前進行的操作 第二種是&#xff1a;通過 在xml中定義init-method 和 destory-method方法 第三種是&#xff1a;…

谷歌F12調試公眾號時,讓鼠標顯示出來

yi 環境介紹: win10 , 谷歌瀏覽器yii 概述: 在項目中,需要調試公眾號,本地環境搭好之后,在谷歌瀏覽時,發現移動到公眾號區域,鼠標居然不見了,這讓我怎么操作?各種操作可謂是日了狗了,非常麻煩yiii 調試時鼠標不見的解決辦法: 網上各種說法眾說紛紜,這里,我給出本人認為最恰當簡…