程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 學習ASP.NET Core, 怎能不了解請求處理管道[3]: 自定義一個服務器感受一下管道是如何監聽、接收和響應請求的,

學習ASP.NET Core, 怎能不了解請求處理管道[3]: 自定義一個服務器感受一下管道是如何監聽、接收和響應請求的,

編輯:關於.NET

學習ASP.NET Core, 怎能不了解請求處理管道[3]: 自定義一個服務器感受一下管道是如何監聽、接收和響應請求的,


我們在《服務器在管道中的“龍頭”地位》中對ASP.NET Core默認提供的具有跨平台能力的KestrelServer進行了介紹,為了讓讀者朋友們對管道中的服務器具有更加深刻的認識,接下來我們采用實例演示的形式創建一個自定義的服務器。這個自定義的服務器直接利用HttpListener來完成針對請求的監聽、接收和響應,我們將其命名為HttpListenerServer。在正式介紹HttpListenerServer的設計和實現之前,我們先來顯示一下如何將它應用到 一個具體的Web應用中。我們依然采用最簡單的Hello World應用來演示針對HttpListenerServer的應用,所以我們在Startup類的Configure方法中編寫如下的程序直接響應一個“Hello World”字符串。[本文已經同步到《ASP.NET Core框架揭秘》之中]

   1: public class Startup
   2: {
   3:     public void Configure(IApplicationBuilder app)
   4:     {
   5:         app.Run(async context => await 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的特性類型,圖7所示的UML基本上體現了HttpListenerServer的總體設計。

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

接下來我們來看看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
  31:              .First(address => httpListenerContext.Request.Url.IsBaseOf(new Uri(address)));
  32:             string pathBase = new Uri(listenUrl).LocalPath.TrimEnd('/') ;
  33:             HttpListenerServerFeature feature = new HttpListenerServerFeature(httpListenerContext, pathBase);
  34:  
  35:             FeatureCollection features = new FeatureCollection();
  36:             features.Set<IHttpRequestFeature>(feature);
  37:             features.Set<IHttpResponseFeature>(feature);
  38:             TContext context = application.CreateContext(features);
  39:  
  40:             application.ProcessRequestAsync(context).ContinueWith(task =>
  41:             {
  42:                 httpListenerContext.Response.Close();
  43:                 application.DisposeContext(context, task.Exception);
  44:             });
  45:         }
  46:     }
  47: }

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved