如果你想深刻理解ASP.NET Core請求處理管道,可以試著寫一個自定義的Server

来源:http://www.cnblogs.com/wuzhe890919/archive/2017/04/28/6782165.html
-Advertisement-
Play Games

我們在上面對ASP.NET Core預設提供的具有跨平臺能力的KestrelServer進行了詳細介紹(《聊聊ASP.NET Core預設提供的這個跨平臺的伺服器——KestrelServer》),為了讓讀者朋友們對管道中的Server具有更加深刻的認識,接下來我們採用實例演示的形式創建一個自定義的 ...


我們在上面對ASP.NET Core預設提供的具有跨平臺能力的KestrelServer進行了詳細介紹(《聊聊ASP.NET Core預設提供的這個跨平臺的伺服器——KestrelServer》),為了讓讀者朋友們對管道中的Server具有更加深刻的認識,接下來我們採用實例演示的形式創建一個自定義的Server。這個自定義的Server直接利用HttpListener來完成針對請求的監聽、接收和響應,我們將其命名為HttpListenerServer。在正式介紹HttpListenerServer的設計和實現之前,我們先來顯示一下如何將它應用到 一個具體的Web應用中。

一、HttpListenerServer的使用

我們依然採用最簡單的Hello World應用來演示針對HttpListenerServer的應用,所以我們在Startup類的Configure方法中編寫如下的程式直接響應一個“Hello World”字元串。

   1: public class Startup
   2: {
   3:     public void Configure(IApplicationBuilder app)
   4:     {
   5:         app.Run(context => context.Response.WriteAsync("Hello World!"));
   6:     }
   7: }

在作為程式入口的Main方法中,我們直接創建一個WebHostBuilder對象並調用擴展方法UseHttpListener完成針對自定義HttpListenerServer的註冊。我們接下來調用UseStartup方法註冊上面定義的這個啟動類型,然後調用Build方法創建一個WebHost對象,最後調用Run方法運行這個作為宿主的WebHost。

   1: public class Program
   2: {
   3:     public static void Main()
   4:     {
   5:         new WebHostBuilder()
   6:             .UseHttpListener()
   7:             .UseStartup<Startup>()
   8:             .Build()
   9:             .Run();
  10:     }
  11: }
  12:  
  13: public static class WebHostBuilderExtensions
  14: {
  15:     public static IWebHostBuilder UseHttpListener(this IWebHostBuilder builder)
  16:     {
  17:         builder.ConfigureServices(services => services.AddSingleton<IServer, HttpListenerServer>());
  18:         return builder;
  19:     }
  20: }

我們自定義的擴展方法UseHttpListener的邏輯很簡單,它只是調用WebHostBuilder的ConfigureServices方法將我們自定義的HttpListenerServer類型以單例模式註冊到指定的ServiceCollection上而已。我們直接運行這個程式並利用瀏覽器訪問預設的監聽地址(http://localhost:5000),服務端響應的“Hello World”字元串會按照如下圖所示的形式顯示在瀏覽器上。

 

 

二、總體設計

接下來我們來介紹一下HttpListenerServer的大體涉及。除了HttpListenerServer這個實現了IServer的自定義Server類型之外,我們只定義了一個名為HttpListenerServerFeature的特性類型,下圖所示的UML基本上體現了HttpListenerServer的總體設計。

 

 

三、HttpListenerServerFeature

如果我們利用HttpListener來監聽請求,它會為接收到的每次請求創建一個屬於自己的上下文,具體來說這是一個類型為HttpListenerContext對象。我們可以利用這個HttpListenerContext對象獲取所有與請求相關的信息,針對請求的任何響應也都是利用它完成的。上面這個HttpListenerServerFeature實際上就是對這個作為原始上下文的HttpListenerContext對象的封裝,或者說它是管道使用的DefaultHttpContext與這個原始上下文之間溝通的中介。

如下所示的代碼片段展示了HttpListenerServerFeature類型的完整定義。簡單起見,我們並沒有實現上面提到過的所有特性介面,而只是選擇性地實現了IHttpRequestFeature和IHttpResponseFeature這兩個最為核心的特性介面。它的構造函數除了具有一個類型為HttpListenerContext的參數之外,還具有一個字元串的參數pathBase用來指定請求URL的基地址(對應IHttpRequestFeature的PathBase屬性),我們利用它來計算請求URL的相對地址(對應IHttpRequestFeature的Path屬性)。IHttpRequestFeature和IHttpResponseFeature中定義的屬性都可以直接利用HttpListenerContext對應的成員來實現,這方面並沒有什麼特別之處。

   1: public class HttpListenerServerFeature : IHttpRequestFeature, IHttpResponseFeature
   2: {
   3:     private readonly HttpListenerContext     httpListenerContext;
   4:     private string            queryString;
   5:     private IHeaderDictionary         requestHeaders;
   6:     private IHeaderDictionary         responseHeaders;
   7:     private string            protocol;
   8:     private readonly string       pathBase;
   9:  
  10:     public HttpListenerServerFeature(HttpListenerContext httpListenerContext, string pathBase)
  11:     {
  12:         this.httpListenerContext     = httpListenerContext;
  13:         this.pathBase         = pathBase;
  14:     }
  15:  
  16:     #region IHttpRequestFeature
  17:  
  18:     Stream IHttpRequestFeature.Body
  19:     {
  20:         get { return httpListenerContext.Request.InputStream; }
  21:         set { throw new NotImplementedException(); }
  22:     }
  23:  
  24:     IHeaderDictionary IHttpRequestFeature.Headers
  25:     {
  26:         get { return requestHeaders 
  27:          ?? (requestHeaders = GetHttpHeaders(httpListenerContext.Request.Headers)); }
  28:         set { throw new NotImplementedException(); }
  29:     }
  30:  
  31:     string IHttpRequestFeature.Method
  32:     {
  33:         get { return httpListenerContext.Request.HttpMethod; }
  34:         set { throw new NotImplementedException(); }
  35:     }
  36:  
  37:     string IHttpRequestFeature.Path
  38:     {
  39:         get { return httpListenerContext.Request.RawUrl.Substring(pathBase.Length);}
  40:         set { throw new NotImplementedException(); }
  41:     }
  42:  
  43:     string IHttpRequestFeature.PathBase
  44:     {
  45:         get { return pathBase; }
  46:         set { throw new NotImplementedException(); }
  47:     }
  48:  
  49:     string IHttpRequestFeature.Protocol
  50:     {
  51:         get{ return protocol ?? (protocol = this.GetProtocol());}
  52:         set { throw new NotImplementedException(); }
  53:     }
  54:  
  55:     string IHttpRequestFeature.QueryString
  56:     {
  57:         Get { return queryString ?? (queryString = this.ResolveQueryString());}
  58:         set { throw new NotImplementedException(); }
  59:     }
  60:  
  61:     string IHttpRequestFeature.Scheme
  62:     {
  63:         get { return httpListenerContext.Request.IsWebSocketRequest ? "https" : "http"; }
  64:         set { throw new NotImplementedException(); }
  65:     }
  66:     #endregion
  67:  
  68:     #region IHttpResponseFeature
  69:     Stream IHttpResponseFeature.Body
  70:     {
  71:         get { return httpListenerContext.Response.OutputStream; }
  72:         set { throw new NotImplementedException(); }
  73:     }
  74:  
  75:     string IHttpResponseFeature.ReasonPhrase
  76:     {
  77:         get { return httpListenerContext.Response.StatusDescription; }
  78:         set { httpListenerContext.Response.StatusDescription = value; }
  79:     }
  80:  
  81:     bool IHttpResponseFeature.HasStarted
  82:     {
  83:         get { return httpListenerContext.Response.SendChunked; }
  84:     }
  85:  
  86:     IHeaderDictionary IHttpResponseFeature.Headers
  87:     {
  88:         get { return responseHeaders 
  89:             ?? (responseHeaders = GetHttpHeaders(httpListenerContext.Response.Headers)); }
  90:         set { throw new NotImplementedException(); }
  91:     }
  92:     int IHttpResponseFeature.StatusCode
  93:     {
  94:         get { return httpListenerContext.Response.StatusCode; }
  95:         set { httpListenerContext.Response.StatusCode = value; }
  96:     }
  97:  
  98:     void IHttpResponseFeature.OnCompleted(Func<object, Task> callback, object state)
  99:     {
 100:         throw new NotImplementedException();
 101:     }
 102:  
 103:     void IHttpResponseFeature.OnStarting(Func<object, Task> callback, object state)
 104:     {
 105:         throw new NotImplementedException();
 106:     }
 107:     #endregion
 108:  
 109:     private string ResolveQueryString()
 110:     {
 111:         string queryString = "";
 112:         var collection = httpListenerContext.Request.QueryString;
 113:         for (int i = 0; i < collection.Count; i++)
 114:         {
 115:             queryString += $"{collection.GetKey(i)}={collection.Get(i)}&";
 116:         }
 117:         return queryString.TrimEnd('&');
 118:     }
 119:  
 120:     private IHeaderDictionary GetHttpHeaders(NameValueCollection headers)
 121:     {
 122:         HeaderDictionary dictionary = new HeaderDictionary();
 123:         foreach (string name in headers.Keys)
 124:         {
 125:             dictionary[name] = new StringValues(headers.GetValues(name));
 126:         }
 127:         return dictionary;
 128:     }
 129:  
 130:     private string GetProtocol()
 131:     {
 132:         HttpListenerRequest request = httpListenerContext.Request;
 133:         Version version = request.ProtocolVersion;
 134:         return string.Format("{0}/{1}.{2}", request.IsWebSocketRequest ? "HTTPS" : "HTTP", version.Major, version.Minor);
 135:     }
 136: }


四、HttpListenerServer

接下來我們來看看HttpListenerServer的定義。如下麵的代碼片段所示,用來監聽請求的HttpListener在構造函數中被創建,與此同時,我們會創建一個用於獲取監聽地址的ServerAddressesFeature對象並將其添加到屬於自己的特性列表中。當HttpListenerServer隨著Start方法的調用而被啟動後,它將這個ServerAddressesFeature對象提取出來,然後利用它得到所有的地址並添加到HttpListener的Prefixes屬性表示的監聽地址列表中。接下來,HttpListener的Start方法被調用,併在一個無限迴圈中開啟請求的監聽與接收。

   1: public class HttpListenerServer : IServer
   2: {
   3:     private readonly HttpListener listener;
   4:  
   5:     public IFeatureCollection Features { get; } = new FeatureCollection();
   6:     
   7:     public HttpListenerServer()
   8:     {
   9:         listener = new HttpListener();
  10:         this.Features.Set<IServerAddressesFeature>(new ServerAddressesFeature());
  11:     }
  12:  
  13:     public void Dispose()
  14:     {
  15:         listener.Stop();
  16:     }
  17:  
  18:     public void Start<TContext>(IHttpApplication<TContext> application)
  19:     {
  20:         foreach (string address in this.Features.Get<IServerAddressesFeature>().Addresses)
  21:         {
  22:             listener.Prefixes.Add(address.TrimEnd('/') + "/");
  23:         }
  24:  
  25:         listener.Start();
  26:         while (true)
  27:         {
  28:             HttpListenerContext httpListenerContext = listener.GetContext();
  29:  
  30:             string listenUrl = this.Features.Get<IServerAddressesFeature>().Addresses.First(address => httpListenerContext.Request.Url.IsBaseOf(new Uri(address)));
  31:             string pathBase = new Uri(listenUrl).LocalPath.TrimEnd('/') ;
  32:             HttpListenerServerFeature feature = new HttpListenerServerFeature(httpListenerContext, pathBase);
  33:  
  34:             FeatureCollection features = new FeatureCollection();
  35:             features.Set<IHttpRequestFeature>(feature);
  36:             features.Set<IHttpResponseFeature>(feature);
  37:             TContext context = application.CreateContext(features);
  38:  
  39:             application.ProcessRequestAsync(context).ContinueWith(task =>
  40:             {
  41:                 httpListenerContext.Response.Close();
  42:                 application.DisposeContext(context, task.Exception);
  43:             });
  44:         }
  45:     }
  46: }

HttpListener的GetContext方法以同步的方式監聽請求,並利用接收到的請求創建返回的HttpListenerContext對象。我們利用它解析出當前請求的基地址,併進一步創建出描述當前原始上下文的HttpListenerServerFeature。接下來我們將這個對象分別採用特性介面IHttpRequestFeature和IHttpResponseFeature添加到創建的FeatureCollection對象中。然後我們將這個FeatureCollection作為參數調用HttpApplication的CreateContext創建出上下文對象,並將其作為參數調用HttpApplication的ProcessContext方法讓註冊的中間件來逐個地對請求進行處理。

參考頁面:http://qingqingquege.cnblogs.com/p/5933752.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、前言 我們在優化Web服務的時候,對於靜態的資源文件,通常都是通過客戶端緩存、伺服器緩存、CDN緩存,這三種方式來緩解客戶端對於Web伺服器的連接請求壓力的。 本文指在這三個方面,在ASP.NET Core中靜態文件的實現過程和使用方法進行闡述。當然也可以考慮使用反向代理的方式(例如IIS或Ng ...
  • 這裡的“私闖sys.databases”是指Entity Framework預設發起的查詢:SELECT Count(*) FROM sys.databases WHERE [name]=N'資料庫名' 註:本文針對的是Entity Framework Code First場景,Entity Fra ...
  • 最近做一個指紋採集和比對的功能,因為公司整個項目是WEB類型的,所以指紋採集的模塊要嵌套在網頁中,那隻有用ActiveX了,以下是一些操作及效果,做個筆記! 新建用戶控制項,編寫CS代碼,如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ...
  • Visual Studio 我們在Windows平臺上開發應用程式使用的工具主要是Visual Studio.這個集成開發環境已經演化了很多年,從一個簡單的C++編輯器和編譯器到一個高度集成、支持軟體開發整個生命周期的多語言環境。 Visual Studio以及它發佈的工具和服務提供了:設計、開發、 ...
  • 在開發Windows服務程式時,我們一般需要添加安裝程式,即:serviceInstaller,裡面有幾個關於名稱屬性,你都搞明白了嗎? 1.Description:表示服務說明(描述服務是乾什麼的); 2.DisplayName:表示友好名稱,可以理解為服務名的別名; 3.ServiceName: ...
  • 從編程的角度來講,ASP.NET Web API針對CORS的實現僅僅涉及到HttpConfiguration的擴展方法EnableCors和EnableCorsAttribute特性。但是整個CORS體系不限於此,在它們背後隱藏著一系列的類型,我們將會利用本章餘下的內容對此作全面講述,今天我們就來 ...
  • 為什麼要使用StringBuilder 為什麼要使用StringBuilder 為什麼要使用StringBuilder 為什麼使用StringBuilder要從string對象的特性說起。 為什麼使用StringBuilder要從string對象的特性說起。 string對象在進行字元串拼接時,因為 ...
  • 簡介 簡介 對於.net來說,用web api來構建服務是一個不錯的選擇,都是http請求,調用簡單,但是如果真的要在程式中調用,則還有些工作要做,比如我們需要手寫httpClient調用,並映射Model, 如果服務少還可以,多了就繁瑣了。 Swagger Swagger 關於Swagger的信息 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...