構成asp.net Web API核心框架的消息處理管道既不關心請求消息來源于何處,也不需要考慮響應消息歸于何方。當我們采用Web Host模式將一個ASP.NET應用作為目標Web API的宿主時,實際上是由ASP.NET管道解決了這兩個問題。具體來說,ASP.NET自身的URL路由系統借助于HttpControllerHandler這個自定義的HttpHandler實現了ASP.NET管道和ASP.NET Web API管道之間的“連通”,但是在Self Host寄宿模式下,請求的監聽、接收和響應又是如何實現的呢?[本文已經同步到《How ASP.NET Web API Works?》]
目錄 一、HttpBinding模型 Binding模型 HttpBinding 實例演示:直接利用HttpBinding進行請求的接收和響應 二、HttpSelfHostServer HttpSelfHostConfiguration HttpSelfHostServer與消息處理管道 實例演示:創建自定義HttpServer模擬HttpSelfHostServer的工作原理
一、HttpBinding模型
對于WCF具有基本了解的讀者應該都知道,它是一個基于消息的分布式通信框架,消息交換借助于客戶端和服務端對等的終結點(Endpoint)來完成,而終結點由經典的ABC(Address、Binding、Contract)三元素組成。WCF同樣具有一個處理消息的管道,這個管道是一組Channel的有序組合,WCF下的Channel相對于ASP.NET Web API下的HttpMessageHandler。
WCF的消息處理管道的締造者是作為終結點三要素之一的Binding。Binding不僅僅為服務端創建用于接收請求回復響應的管道,同時也為客戶端創建發送請求接收響應的管道。Binding模型本身也相對比較復雜,所以我們不可能對其進行詳細討論。如果讀者對此比較感興趣,可以參閱《WCF的綁定模型》。由于ASP.NET Web API只是利用HttpBinding創建服務端消息處理管道,所以我們只討論Binding的服務端模型。
從結構上講,一個Binding是若干BindingElement對象的有序組合。對于最終創建的消息處理管道來說,每個Channel都對應著一個BindingElement。BindingElement并非直接創建對應的Channel,由它直接創建的實際上是一個名為ChannelListener的對象,Channel由ChannelListener創建。右圖基本揭示了Binding的服務端模型。
顧名思義,ChannelListener用于請求的監聽。當Binding對象開啟(調用其Open方法)時,每個BindingElement會創建各自的ChannelListener。這些ChannelListener按照對應BindingElement的順序連接成串,位于底部(面向傳輸層)的ChannelListener被綁定到某個端口進行請求的監聽。一旦探測到抵達的請求,它會利用由所有ChannelListener創建的Channel組成的管道來接收并處理該請求。對于最終需要返回的響應消息,則按照從上到下的順序被這個管道進行處理并最終返回給客戶端。
對于這個由Channel組成消息處理管道來說,有兩種類型的Channel是必不可少的。一種是面向傳輸層用于發送和接收消息的TransportChannel,另一種被稱為MessageEncodingChannel則負責對接收的消息實施解碼并對發送的消息實施編碼。TransportChannel由TransportChannelListener創建,而后者由TransportBindingElement創建。與之類似,MessageEncodingBindingElement是MessageEncodingChannelListener的創建者,而后者又是MessageEncodingChannel的創建者。
如果采用Self Host寄宿模式,請求的監聽是由一個類型為HttpBinding的Binding對象創建的ChannelListener管道來完成的,由它創建的管道實現了針對請求的接收和針對響應的回復。HttpBinding類型定義在“System.Web.Http.SelfHost.Channels”命名空間下,我們接下來對它進行詳細講述。
Binding存在的目的在于創建用于處理和傳輸消息的信道棧,組成信道棧的每一個Channel均對應著一個BindingElement,所以Binding本身處理消息的能力由其BindingElement的組成來決定,我們可以通過分析BindingElement的組成來了解消息最終是如何處理的。現在我們就來討論一下ASP.NET Web API在Self Host模式下使用的HttpBinding由哪些BindingElement構成。
如左圖所示,HttpBinding僅僅由兩種必需的BindingElement構成,TransportBindingElement的類型決定于最終采用的傳輸協議。如果采用單純的HTTP協議,采用的TransportBindingElement是一個HttpTransportBindingElement對象。在采用HTTPS協議的情況下,TransportBindingElement的類型是HttpsTransportBindingElement。
我們現在著重來分析與消息編碼/解碼相關的BindingElement,從圖3-11可以看出這是一個HttpMessageEncodingBindingElement對象(HttpMessageEncodingBindingElement是一個定義在程序集“System.Web.Http.SelfHost.dll”中的內部類型),它最終會創建一個MessageEncoder對象完成針對消息的編碼/解碼工作。
ASP.NET Web API分別利用 HttPRequestMessage和HttpResponseMessage對象表示消息處理管道處理的請求和響應,而WCF消息處理管道的請求和響應均是一個Message對象(Message是定義在命名空間“System.ServiceModel.Channels”下的一個抽象類型)。經過HttpMessageEncoder解碼后的Message對象會轉成一個HttpRequestMessage對象并傳入ASP.NET Web API消息處理管道進行處理,由此管道返回的HttpResponseMessage對象需要轉換成一個Message對象并由HttpMessageEncoder根據需求進行解碼。
這個具體的消息實際上是一個HttpMessage對象,HttpMessage繼承自抽象類Message,它是一個定義在程序集“System.Web.Http.SelfHost.dll”中的內部類型。如下面的代碼片斷所示,HttpMessage實際上是對一個HttpRequestMessage或者HttpResponseMessage對象的封裝,兩個方法GetHttpRequestMessage和GetHttpResponseMessage分別用于提取被封裝的HttpRequestMessage和HttpResponseMessage對象。
1: internal sealed class HttpMessage : Message
2: {
3: //其他成員
4: public HttpMessage(HttpRequestMessage request);
5: public HttpMessage(HttpResponseMessage response);
6:
7: public HttpRequestMessage GetHttpRequestMessage(bool extract);
8: public HttpResponseMessage GetHttpResponseMessage(bool extract);
9: }
這兩個方法均具有一個布爾類型的參數extract,它表示是否“抽取”被封裝的HttpRequestMessage/HttpResponseMessage對象。如果指定的參數值為True,方法執行之后被封裝的HttpRequestMessage/HttpResponseMessage對象會從HttpMessage對象中抽取出來,所以再次調用它們會返回Null。
再次將我們的關注點拉回到由HttpBinding創建的消息處理管道上。當我們開啟HttpBinding后,它利用創建的ChannelListener管道監聽請求。一旦探測到抵達的請求后,基于HTTP/HTTPS協議的TransportChannel會負責接收請求。接收的二進制數據會由MessageEncoder解碼后生成一個HttpRequestMessage對象,該對象進而被封裝成一個HttpMessage對象,傳入消息處理管道的HttpRequestMessage是直接通過調用GetHttpRequestMessage方法從該HttpMessage對象中提取的。
當ASP.NET Web API消息處理管道完成了請求的處理并最終輸出一個HttpResponseMessage對象后,該對象同樣先被封裝成一個HttpMessage對象。在通過傳輸層將響應返回給客戶端之前,需要利用MessageEncoder對其進行編碼,而解碼的內容實際上就是調用GetHttpResponseMessage方法提取的HttpResponseMessage對象。
當我們采用Self Host寄宿模式將一個非Web應用程序作為目標Web API的宿主時,最終網絡監聽任務實際上是由HttpBinding創建的ChannelListener管道來完成的,而ChannelListener管道創建的消息處理管道最終實現了對請求的接收和對響應的發送。為了讓讀者對此具有深刻的認識,我們通過一個簡單的實例來演示如何直接使用HttpBinding實現對請求的監聽、接收和響應。
我們創建一個空的控制臺程序作為監聽服務器,它相當于Self Host寄宿模式下的宿主程序。如下面的代碼片斷所示,我們創建了一個HttpBinding,并指定監聽地址("http://127.0.0.1:3721")調用其BuildChannelListener<IReplyChannel>方法創建了一個ChannelListener管道(返回的是組成管道的第一個ChannelListener對象)。在調用Open方法開啟該ChannelListener管道之后,我們調用其AcceptChannel方法創建了消息處理管道,返回的是組成管道的第一個Channel對象。在Open方法將其開啟后,我們在一個While循環中調用Channel對象的ReceiveRequest方法進行請求的監聽和接收。
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: Uri listenUri = new Uri("http://127.0.0.1:3721");
6: Binding binding = new HttpBinding();
7:
8: //創建、開啟信道監聽器
9: IChannelListener<IReplyChannel> channelListener = binding.BuildChannelListener<IReplyChannel>(listenUri);
10: channelListener.Open();
11:
12: //創建、開啟回復信道
13: IReplyChannel channel = channelListener.AcceptChannel(TimeSpan.MaxValue);
14: channel.Open();
15:
16: //開始監聽
17: while (true)
18: {
19: //接收輸出請求消息
20: RequestContext requestContext = channel.ReceiveRequest(TimeSpan.MaxValue);
21: PrintRequestMessage(requestContext.RequestMessage);
22: //消息回復
23: requestContext.Reply(CreateResponseMessage());
24: }
25: }
26: }
對于成功接收的消息,我們調用具有如下定義的PrintRequestMessage方法將相關的信息打印在控制臺上。通過上面的介紹我們知道這個接收到的消息實際上是一個HttpMessage對象,由于這是一個內部類型,所以我們只能以反射的方式調用其GetHttpRequestMessage方法獲取被封裝的HttpRequestMessage對象。在得到表示請求的HttpRequestMessage對象之后,我們將請求地址和所有報頭輸出到控制臺上。
1: private static void PrintRequestMessage(Message message)
2: {
3: MethodInfo method = message.GetType().GetMethod("GetHttpRequestMessage");
4: HttpRequestMessage request = (HttpRequestMessage)method.Invoke(message, new object[]{false});
5:
6: Console.WriteLine("{0, -15}:{1}", "RequestUri", request.RequestUri);
7: foreach (var header in request.Headers)
8: {
9: Console.WriteLine("{0, -15}:{1}", header.Key, string.Join("," ,header.Value.ToArray()));
10: }
11: }
在對請求進行處理之后,我們需要創建一個Message對象對該請求予以響應,響應消息的創建是通過CreateResponseMessage方法完成的。如下面的代碼片斷所示,我們首先創建了一個響應狀態為“200, OK”的HttpResponseMessage對象,并將其表示主體內容的Content屬性設置為一個ObjectContent<Em
|
新聞熱點
疑難解答