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