《ASP.NET Core 6框架揭秘》實例演示[28]:自定義一個服務器

作為ASP.NET Core請求處理管道的“龍頭”的服務器負責監聽和接收請求并最終完成對請求的響應。它將原始的請求上下文描述為相應的特性(Feature),并以此將HttpContext上下文創建出來,中間件針對HttpContext上下文的所有操作將借助于這些特性轉移到原始的請求上下文上。學習ASP.NET Core框架最有效的方式就是按照它的原理“再造”一個框架,了解服務器的本質最好的手段就是試著自定義一個服務器。現在我們自定義一個真正的服務器。在此之前,我們再來回顧一下表示服務器的IServer接口。[本文節選《ASP.NET Core 6框架揭秘》第18章]

一、IServer
二、請求和響應特性
三、StreamBodyFeature
四、HttpListenerServer

一、IServer

作為服務器的IServer對象利用如下所示的Features屬性提供了與自身相關的特性。除了利用StartAsync<TContext>和StopAsync方法啟動和關閉服務器之外,它還實現了IDisposable接口,資源的釋放工作可以通過實現的Dispose方法來完成。StartAsync<TContext>方法將IHttpApplication<TContext>類型的參數作為處理請求的“應用”,該對象是對中間件管道的封裝。從這個意義上講,服務器就是傳輸層和這個IHttpApplication<TContext>對象之間的“中介”。

public?interface?IServer?:?IDisposable
{IFeatureCollection?Features?{?get;?}Task?StartAsync<TContext>(IHttpApplication<TContext>?application,?CancellationToken?cancellationToken)?where?TContext?:?notnull;Task?StopAsync(CancellationToken?cancellationToken);
}

雖然不同服務器類型的定義方式千差萬別,但是背后的模式基本上與下面這個以偽代碼定義的服務器類型一致。如下這個Server利用IListener對象來監聽和接收請求,該對象是利用構造函數中注入的IListenerFactory工廠根據指定的監聽地址創建出來的。StartAsync<TContext>方法從Features特性集合中提取出IServerAddressesFeature特性,并針對它提供的每個監聽地址創建一個IListener對象。該方法為每個IListener對象開啟一個“接收和處理請求”的循環,循環中的每次迭代都會調用IListener對象的AcceptAsync方法來接收請求,我們利用RequestContext對象來表示請求上下文。

public?class?Server?:?IServer
{private?readonly?IListenerFactory?_listenerFactory;private?readonly?List<IListener>?_listeners?=?new();public?IFeatureCollection?Features?{?get;?}?=?new?FeatureCollection();public?Server(IListenerFactory?listenerFactory)?=>?_listenerFactory?=?listenerFactory;public?async?Task?StartAsync<TContext>(IHttpApplication<TContext>?application,?CancellationToken?cancellationToken)?where?TContext?:?notnull{var?addressFeature?=?Features.Get<IServerAddressesFeature>()!;foreach?(var?address?in?addressFeature.Addresses){var?listener?=?await?_listenerFactory.BindAsync(address);_listeners.Add(listener);_?=?StartAcceptLoopAsync(listener);}async?Task?StartAcceptLoopAsync(IListener?listener){while?(true){var?requestContext?=?await?listener.AcceptAsync();_?=?ProcessRequestAsync(requestContext);}}async?Task?ProcessRequestAsync(RequestContext?requestContext){var?feature?=?new?RequestContextFeature(requestContext);var?contextFeatures?=?new?FeatureCollection();contextFeatures.Set<IHttpRequestFeature>(feature);contextFeatures.Set<IHttpResponseFeature>(feature);contextFeatures.Set<IHttpResponseBodyFeature>(feature);var?context?=?application.CreateContext(contextFeatures);Exception??exception?=?null;try{await?application.ProcessRequestAsync(context);}catch?(Exception?ex){exception?=?ex;}finally{application.DisposeContext(context,?exception);}}}public?Task?StopAsync(CancellationToken?cancellationToken)?=>?Task.WhenAll(_listeners.Select(listener?=>?listener.StopAsync()));public?void?Dispose()?=>?_listeners.ForEach(listener?=>?listener.Dispose());
}public?interface?IListenerFactory
{Task<IListener>?BindAsync(string?listenAddress);
}public?interface?IListener?:?IDisposable
{Task<RequestContext>?AcceptAsync();Task?StopAsync();
}public?class?RequestContext
{...
}public?class?RequestContextFeature?:?IHttpRequestFeature,?IHttpResponseFeature,?IHttpResponseBodyFeature
{public?RequestContextFeature(RequestContext?requestContext);...
}

StartAsync<TContext>方法接下來利用此RequestContext上下文將RequestContextFeature特性創建出來。RequestContextFeature特性類型同時實現了IHttpRequestFeature, IHttpResponseFeature和 IHttpResponseBodyFeature這三個核心接口,我們特性針對這三個接口將特性對象添加到創建的FeatureCollection集合中。特性集合隨后作為參數調用IHttpApplication<TContext>的CreateContext方法將TContext上下文創建出來,后者將進一步作為參數調用另一個ProcessRequestAsync方法將請求分發給中間件管道進行處理。待處理結束,IHttpApplication<TContext>對象的DisposeContext方法被調用,創建的TContext上下文承載的資源得以釋放。

二、請求和響應特性

接下來我們將采用類似的模式來定義一個基于HttpListener的服務器。提供的HttpListenerServer的思路就是利用自定義特性來封裝表示原始請求上下文的HttpListenerContext對象,我們使用HttpRequestFeature和HttpResponseFeature這個兩個現成特性。

public?class?HttpRequestFeature?:?IHttpRequestFeature
{public?string?Protocol?{?get;?set;?}public?string?Scheme?{?get;?set;?}public?string?Method?{?get;?set;?}public?string?PathBase?{?get;?set;?}public?string?Path?{?get;?set;?}public?string?QueryString?{?get;?set;?}public?string?RawTarget?{?get;?set;?}public?IHeaderDictionary?Headers?{?get;?set;?}public?Stream?Body?{?get;?set;?}
}
public?class?HttpResponseFeature?:?IHttpResponseFeature
{public?int?StatusCode?{?get;?set;?}public?string??ReasonPhrase?{?get;?set;?}public?IHeaderDictionary?Headers?{?get;?set;?}public?Stream?Body?{?get;?set;?}public?virtual?bool?HasStarted?=>?false;public?HttpResponseFeature(){StatusCode?=?200;Headers?=?new?HeaderDictionary();Body?=?Stream.Null;}public?virtual?void?OnStarting(Func<object,?Task>?callback,?object?state)?{?}public?virtual?void?OnCompleted(Func<object,?Task>?callback,?object?state)?{?}
}

如果我們使用HttpRequestFeature來描述請求,意味著HttpListener在接受到請求之后需要將請求信息從HttpListenerContext上下文轉移到該特性上。如果使用HttpResponseFeature來描述響應,待中間件管道在完成針對請求的處理后,我們還需要將該特性承載的響應數據應用到HttpListenerContext上下文上。

三、StreamBodyFeature

現在我們有了描述請求和響應的兩個特性,還需要一個描述響應主體的特性,為此我們定義了如下這個StreamBodyFeature特性類型。StreamBodyFeature直接使用構造函數提供的Stream對象作為響應主體的輸出流,并根據該對象創建出Writer屬性返回的PipeWriter對象。本著“一切從簡”的原則,我們并沒有實現用來發送文件的SendFileAsync方法,其他成員也采用最簡單的方式進行了實現。

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

四、HttpListenerServer

在如下這個自定義的HttpListenerServer服務器類型中,與傳輸層交互的HttpListener體現在_listener字段上。服務器在初始化過程中,它的Features屬性返回的IFeatureCollection對象中添加了一個ServerAddressesFeature特性,因為我們需要用它來存放注冊的監聽地址。實現StartAsync<TContext>方法將監聽地址從這個特性中取出來應用到HttpListener對象上。

public?class?HttpListenerServer?:?IServer
{private?readonly?HttpListener?_listener?=?new();public?IFeatureCollection?Features?{?get;?}=?new?FeatureCollection();public?HttpListenerServer()?=>?Features.Set<IServerAddressesFeature>(new?ServerAddressesFeature());public?Task?StartAsync<TContext>(IHttpApplication<TContext>?application,CancellationToken?cancellationToken)?where?TContext?:?notnull{var?pathbases?=?new?HashSet<string>(StringComparer.OrdinalIgnoreCase);var?addressesFeature?=?Features ? ? ? ? ? ?            .Get<IServerAddressesFeature>()!;foreach?(string?address?in?addressesFeature.Addresses){_listener.Prefixes.Add(address.TrimEnd('/')?+?"/");pathbases.Add(new?Uri(address).AbsolutePath.TrimEnd('/'));}_listener.Start();while?(true){var?listenerContext?=?_listener.GetContext();_?=?ProcessRequestAsync(listenerContext);}async?Task?ProcessRequestAsync(?HttpListenerContext?listenerContext){FeatureCollection?features?=?new();var?requestFeature?=?CreateRequestFeature(pathbases,?listenerContext);var?responseFeature?=?new?HttpResponseFeature();var?body?=?new?MemoryStream();var?bodyFeature?=?new?StreamBodyFeature(body);features.Set<IHttpRequestFeature>(requestFeature);features.Set<IHttpResponseFeature>(responseFeature);features.Set<IHttpResponseBodyFeature>(bodyFeature);var?context?=?application.CreateContext(features);Exception??exception?=?null;try{await?application.ProcessRequestAsync(context);var?response?=?listenerContext.Response;response.StatusCode?=?responseFeature.StatusCode;if?(responseFeature.ReasonPhrase?is?not?null){response.StatusDescription?=?responseFeature.ReasonPhrase;}foreach?(var?kv?in?responseFeature.Headers){response.AddHeader(kv.Key,?kv.Value);}body.Position?=?0;await?body.CopyToAsync(listenerContext.Response.OutputStream);}catch?(Exception?ex){exception?=?ex;}finally{body.Dispose();application.DisposeContext(context,?exception);listenerContext.Response.Close();}}}public?void?Dispose()?=>?_listener.Stop();private?static?HttpRequestFeature?CreateRequestFeature(HashSet<string>?pathbases,HttpListenerContext?listenerContext){var?request?=?listenerContext.Request;var?url?=?request.Url!;var?absolutePath?=?url.AbsolutePath;var?protocolVersion?=?request.ProtocolVersion;var?requestHeaders?=?new?HeaderDictionary();foreach?(string?key?in?request.Headers){requestHeaders.Add(key,?request.Headers.GetValues(key));}var?requestFeature?=?new?HttpRequestFeature{Body?=?request.InputStream,Headers?=?requestHeaders,Method?=?request.HttpMethod,QueryString?=?url.Query,Scheme?=?url.Scheme,Protocol?=?$"{url.Scheme.ToUpper()}/{protocolVersion.Major}.{protocolVersion.Minor}"};var?pathBase?=?pathbases.First(it?=>?absolutePath.StartsWith(it,?StringComparison.OrdinalIgnoreCase));requestFeature.Path?=?absolutePath[pathBase.Length..];requestFeature.PathBase?=?pathBase;return?requestFeature;}public?Task?StopAsync(          CancellationToken?cancellationToken){_listener.Stop();return?Task.CompletedTask;}
}

在調用Start方法將HttpListener啟動后,StartAsync<TContext>方法開始“請求接收處理”循環。接收到的請求上下文被封裝成HttpListenerContext上下文,其承載的請求信息利用CreateRequestFeature方法轉移到創建的HttpRequestFeature特性上。StartAsync<TContext>方法創建的“空”HttpResponseFeature對象來描述響應,另一個描述響應主體的StreamBodyFeature特性則根據創建的MemoryStream對象構建而成,意味著中間件管道寫入的響應主體的內容將暫存到這個內存流中。我們將這三個特性注冊到創建的FeatureCollection集合上,并將后者作為參數調用了IHttpApplication<TContext>對象的CreateContext方法將TContext上下文創建出來。此上下文進一步作為參數調用了IHttpApplication<TContext>對象的ProcessRequestAsync方法,中間件管道得以接管請求。

待中間件管道的處理工作完成后,響應的內容還暫存在兩個特性中,我們還需要將它們應用到代表原始HttpListenerContext上下文上。StartAsync<TContext>方法從HttpResponseFeature特性提取出響應狀態碼和響應報頭轉移到HttpListenerContext上下文上,然后上述這個MemoryStream對象“拷貝”到HttpListenerContext上下文承載的響應主體輸出流中。

using?App;
using?Microsoft.AspNetCore.Hosting.Server;
using?Microsoft.Extensions.DependencyInjection.Extensions;var?builder?=?WebApplication.CreateBuilder(args);
builder.Services.Replace(ServiceDescriptor.Singleton<IServer,?HttpListenerServer>());
var?app?=?builder.Build();
app.Run(context?=>?context.Response.WriteAsync("Hello?World!"));
app.Run("http://localhost:5000/foobar/");

我們采用上面的演示程序來檢測HttpListenerServer能否正常工作。我們為HttpListenerServer類型創建了一個ServiceDescriptor對象將現有的服務器的服務注冊替換掉。在調用WebApplication對象的Run方法時顯式指定了具有PathBase(“/foobar”)的監聽地址“http://localhost:5000/foobar/”,如圖1所示的瀏覽器以此地址訪問應用,會得到我們希望的結果。

38d63263ab8511f4c50552bf97f99a41.jpeg
圖1 HttpListenerServer返回的結果

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

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

相關文章

高清攝像頭MIPI接口與ARM連接【轉】

本文轉載自&#xff1a;http://www.cnblogs.com/whw19818/p/5811299.html MIPI攝像頭常見于手機、平板中&#xff0c;支持500萬像素以上高清分辨率。它的全稱為“Mobile Industry Processor Interface”&#xff0c;分為MIPI DSI 和MIPI CSI&#xff0c;分別對應于視頻顯示和視…

算法(第4版)Robert Sedgewick 刷題 第一章(1)

/*** Description 顛倒數組排列順序* author SEELE* date 2017年8月17日 上午10:56:17* action sortArr*/public static void sortArr() {int[] b new int[6];int[] a { 1, 2, 3, 4, 5, 6, 7 };for (int i 0; i < a.length / 2; i) {int temp a[a.length - 1 - i];a[a.l…

9種排序算法在四種數據分布下的速度比較

9種算法分別是&#xff1a; 1.選擇排序 2.希爾排序 3.插入排序 4.歸并排序 5.快速排序 6.堆排序 7.冒泡排序 8.梳排序 9.雞尾酒排序 在不同的情形下&#xff0c;排序速度前三名也不盡相同 Random : 希爾>快排>歸并 Few unique : 快排>…

win7服務器端口被占用,高手親自幫您win7端口被占用的詳盡處理要領

今天有一位用戶說他安裝了win7系統以后&#xff0c;在使用中突然遇到了win7端口被占用的情況&#xff0c;估計還會有更多的網友以后也會遇到win7端口被占用的問題&#xff0c;所以今天我們先來分析分析&#xff0c;那我們要怎么面對這個win7端口被占用的問題呢&#xff1f;大家…

局部變量和參數傳遞的問題

<SCRIPT LANGUAGE"JavaScript">var bb 1;function aa(bb) { //這里傳遞參數相當于 var bb bb ;給形參bb賦值為1。&#xff08;又參數傳遞&#xff0c;相當于就是在函數中定義了一個局部變量并且給這個變量賦了初值1&#xff09;此bb非彼bb&#xff0c;是分別…

回車ajax顯示,ajax返回值中有回車換行、空格的解決方法分享

最近在寫一個頁面&#xff0c;用jquery ajax來實現判斷&#xff0c;剛寫好測試完全沒有問題&#xff0c;過了兩天發現出現問題&#xff0c;判斷不成了。后來發現所有alert出來的返回值前面都會加若干換行和空格。(至今不明白&#xff0c;同一臺電腦&#xff0c;同樣的環境&…

PHP插入排序

本意是想研究一下希爾排序的,因為希爾排序和快速排序沒有爭議的是排序最快的兩種算法,但無奈希爾排序是以插入排序為基礎的,所以只得先研究一下插入排序. 插入排序基本思想: 插入排序(Insertion Sort)的基本思想是&#xff1a;每次將一個待排序的記錄&#xff0c;按其關鍵字大小…

使用Stepping.NET輕松執行多步原子操作

Stepping 是一個基于 BASE 的分布式作業實現。它可以作為工作流引擎&#xff0c;事件收/發件箱&#xff0c;用于郵箱/短信發送&#xff0c;用于遠程接口調用等場景。Stepping 中 Job 和 Step 是什么?Job 是一個分布式事務單元&#xff0c;而 Step 是 job 中一個特定的任務。一…

JSP+JavaBean+Servlet技術(MVC模型)

一&#xff0c;Servlet開發用戶在瀏覽器中輸入一個網址并回車&#xff0c;瀏覽器會向服務器發送一個HTTP請求。服務器端程序接受這個請求&#xff0c;并對請求進行處理&#xff0c;然后發送一個回應。瀏覽器收到回應&#xff0c;再把回應的內容顯示出來。這種請求—響應模式就是…

ora-01591:鎖被未分布式事物處理/Distrib tran

伴隨報錯內容&#xff1a;Distrib tran xxx.xxx.xx.x.xxxx 1、使用Oracle DBA用戶&#xff0c;查詢如下數據字典&#xff1a;select * from dba_2pc_pending2、強制Rollback或者Commit該事務&#xff1a;select commit force || local_tran_id||; from dba_2pc_pending…

bzoj2721 [Violet 5]櫻花

分析&#xff1a;這道題對于我這種蒟蒻來說還是很有難度啊。 思路非常巧妙&#xff0c;既然不定方程要有有限個數解&#xff0c;那么這個肯定會對解有所限制&#xff0c;也就是本題中的正整數.這個時候我們要表示出方程中的一個根x,設z n!,那么xyz/(y-z),這樣的話不能得到答案…

ipados 文件 連接服務器,iPadOS更新指南,總有一個功能是你需要的

近期&#xff0c;蘋果向部分ipad用戶推送了iPadOS系統&#xff0c;據系統介紹&#xff0c;這是一款強大的操作系統&#xff0c;更能體現iPad的獨特之處。iPadOS與IOS同源&#xff0c;針對iPad的大顯示屏和多功能增加了全新和直觀的強大功能。剛才小編給大家提到了部分iPad用戶&…

Angular 2.x 從0到1 (五)史上最簡單的Angular2教程

第一節&#xff1a;Angular 2.0 從0到1 &#xff08;一&#xff09;第二節&#xff1a;Angular 2.0 從0到1 &#xff08;二&#xff09;第三節&#xff1a;Angular 2.0 從0到1 &#xff08;三&#xff09;第四節&#xff1a;Angular 2.0 從0到1 &#xff08;四&#xff09;第五…

《大道至簡》讀后感

所謂的大道至簡就是說大道理&#xff08;基本原理&#xff0c;方法和規律&#xff09;是極其簡單的&#xff0c;簡單到一兩句話就能說明白。所謂“真傳一句話&#xff0c;假傳萬卷書”。這也許也是這本書只有一百多頁的原因吧。 說實話&#xff0c;《大道至簡》這部作品對現在有…

ajax 分頁 評論刷新,評論:js無刷新分頁(原創)

繁華落盡02020/4/28 0:26:00大佬&#xff0c;教一下怎么用&#xff0c;以前我是直接在按鈕上綁個路徑。首頁上一頁${i}${i}下一頁尾頁漫走32020/4/28 20:43:32后臺的方法需要的參數&#xff1a;當前頁、每頁顯示條數&#xff0c;插件都給你控制好了&#xff0c;你直接用就行。e…

MariaDB基礎(二)

MariaDB基礎(二)介紹關于MariaDB的如下知識點&#xff1a;1. 查詢緩存2. 索引3. EXPLAIN1.查詢緩存&#xff1a;1&#xff09;什么是緩存&#xff1f;緩存就是數據交換的緩沖區&#xff0c;即Cache&#xff0c;存放在內存中&#xff1b;2&#xff09;查詢緩存的數據以何種形式存…

設計模式——享元模式具體解釋

0. 前言寫在最前面&#xff0c;本人的設計模式類博文&#xff0c;建議先看博文前半部分的理論介紹。再看后半部分的實例分析。最后再返回來復習一遍理論介紹&#xff0c;這時候你就會發現我在重點處標紅的用心&#xff0c;對于幫助你理解設計模式有奇效哦~本文原創。轉載請注明…

OpenStack Nova計算服務管理(四)

作者&#xff1a;李曉輝聯系方式: Xiaohui_lifoxmail.com環境介紹類型控制節點和計算節點等在一起&#xff0c;形成all-in-one內存8G硬盤200G網卡2塊計算服務概覽使用OpenStack計算服務來托管和管理云計算系統。OpenStack計算服務是基礎設施即服務(IaaS)系統的主要部分&#xf…

miui替換官方文件解決無服務器,miui 關掉云服務器

miui 關掉云服務器 內容精選換一換本節操作介紹Linux云服務器切換密鑰登錄為密碼登錄的操作步驟。使用密鑰登錄Linux云服務器&#xff0c;設置root密碼。sudo passwd root若密鑰文件丟失或損壞&#xff0c;請參考Linux云服務器如何進入單用戶模式重置root密碼&#xff0c;重置r…

PHP-高并發和大流量的解決方案

一 高并發的概念 在互聯網時代&#xff0c;并發&#xff0c;高并發通常是指并發訪問。也就是在某個時間點&#xff0c;有多少個訪問同時到來。 二 高并發架構相關概念 1、QPS (每秒查詢率) : 每秒鐘請求或者查詢的數量&#xff0c;在互聯網領域&#xff0c;指每秒響應請求數…