摘要:介紹 ASP.NET Web API 訊息處理器(message handlers)的基礎概念,並提供一個簡易的實作範例,可將任何 HTTP 請求的內容寫入 log 檔。
概觀
ASP.NET Web API 是以 HttpRequestMessage 類別來代表用戶端發出的 HTTP 請求,並以 HttpResponseMessage 類別來代表伺服器端的回應結果。也就是說,ASP.NET Web API 從收到用戶端請求開始到產生回應結果的過程當中,會在某個適當時間分別建立 HttpRequestMessage 和 HttpResponseMessage 物件實體。這個收到請求、處理請求、至產生回應的過程,一般稱為管線(pipeline)。
稱為管線,因為 HTTP 請求就像水一般從第一條水管流入,行經數個相互銜接的水管,直到最後一個;處理完請求並產生回應之後,接著又讓回應結果循原路回去,流經先前的各條水管。在這個過程當中,我們也可以安插自己的水管,在裡面動點手腳。
這一截截的「水管」對應到 Web API 的實作,就是訊息處理器(message handler)了。下圖概略描繪了 ASP.NET Web API 的「請求-回應」管線:
圖中的 HttpClient 是個 .NET 類別,但在這裡僅代表某個 HTTP 用戶端。它也是 .NET 4.5 的新成員(若為 .NET 4,可透過 NuGet 取得),隸屬 System.Net.Http 命名空間,功能有點類似 HttpWebRequest 和 WebClient。由於 HttpClient 並非本文主角,就此簡短交代過去。
圖中的 HttpServer 類別隸屬 System.Web.Http 命名空間,其父類別是 DelegatingHandler,祖父則是抽象類別 HttpMessageHandler。也就是說,HttpSever 本身就是個訊息處理器。根據 MSDN 網站的說明,其責任是分派 HttpRequestMessage,以及建立 HttpResponseMessage。
DelegatingHandler
DelegatingHandler 的用途是處理 HttpRequestMessage 和 HttpResponseMessage 物件,並將處理過的物件交給下一棒繼續處理(於是形成了前面提過的管線機制)。當然,在處理的時候,也可以不要傳遞給下一個處理器,亦即停止後續的管線。
DelegatingHandler 有個 InnerHandler 屬性,型別是 HttpMessageHandler(它就是 DelegatingHandler 的父類別)。這個 InnerHandler 會指向下一條管線──這裡稍微改個說法:InnerHandler 會指向下一層管線。所以,你應該可以想像得到,就像拆禮物時,外包裝盒打開,裡面還有個盒子,再打開,裡面又還有個盒子…如此層層串接,各層之間的聯繫就是透過 InnerHandler 屬性。你也可以把它想成俄羅斯娃娃的結構,如果你知道這玩意的話。
OK! 文字說明的部分差不多夠了,實作看看吧。
撰寫訊息處理器
之前遇過幾次,撰寫用戶端應用程式的開發人員一直收不到正確結果,老懷疑是 Web API 有 bug 而不肯檢視自己發出的請求內容是否有問題。最後只得將 HTTP 請求的詳細內容全部寫入 log 檔,作為證據。這裡就用一個簡單的訊息處理器來示範如何記錄 HTTP 請求的內容,程式碼是參考自 WebAPIContrib 裡面的 LoggingHandler,只是簡化了些 。
我們的自訂訊息處理器要繼承自前面提過的 DelegatingHandler。我先建立一個 ASP.NET MVC 4 應用程式專案:MessageHandlerDemo。接著在專案的根目錄下建一個 Handlers 子目錄,用來放自己寫的訊息處理器:RequestLogHandler。
建立 RequestLogHandler 類別之後,令它繼承自 DelegatingHandler,然後改寫(override)SendAsync 方法。程式碼如下:
程式碼的邏輯很簡單,就不多解釋了。
註冊訊息處理器
訊息處理器寫好之後,還得註冊,才會起作用。我將註冊的動作寫在 App_Start\WebApiConfig.cs 裡面。參考下列程式碼:
執行此範例程式時,每當用戶端發出請求,C:\Temp 目錄下就會產生對應的 log 檔案。檔案內容是 JSON 格式的字串。
最後附上一張類別圖:
小結
概觀
ASP.NET Web API 是以 HttpRequestMessage 類別來代表用戶端發出的 HTTP 請求,並以 HttpResponseMessage 類別來代表伺服器端的回應結果。也就是說,ASP.NET Web API 從收到用戶端請求開始到產生回應結果的過程當中,會在某個適當時間分別建立 HttpRequestMessage 和 HttpResponseMessage 物件實體。這個收到請求、處理請求、至產生回應的過程,一般稱為管線(pipeline)。
稱為管線,因為 HTTP 請求就像水一般從第一條水管流入,行經數個相互銜接的水管,直到最後一個;處理完請求並產生回應之後,接著又讓回應結果循原路回去,流經先前的各條水管。在這個過程當中,我們也可以安插自己的水管,在裡面動點手腳。
這一截截的「水管」對應到 Web API 的實作,就是訊息處理器(message handler)了。下圖概略描繪了 ASP.NET Web API 的「請求-回應」管線:
圖中的 HttpClient 是個 .NET 類別,但在這裡僅代表某個 HTTP 用戶端。它也是 .NET 4.5 的新成員(若為 .NET 4,可透過 NuGet 取得),隸屬 System.Net.Http 命名空間,功能有點類似 HttpWebRequest 和 WebClient。由於 HttpClient 並非本文主角,就此簡短交代過去。
圖中的 HttpServer 類別隸屬 System.Web.Http 命名空間,其父類別是 DelegatingHandler,祖父則是抽象類別 HttpMessageHandler。也就是說,HttpSever 本身就是個訊息處理器。根據 MSDN 網站的說明,其責任是分派 HttpRequestMessage,以及建立 HttpResponseMessage。
DelegatingHandler
DelegatingHandler 的用途是處理 HttpRequestMessage 和 HttpResponseMessage 物件,並將處理過的物件交給下一棒繼續處理(於是形成了前面提過的管線機制)。當然,在處理的時候,也可以不要傳遞給下一個處理器,亦即停止後續的管線。
DelegatingHandler 有個 InnerHandler 屬性,型別是 HttpMessageHandler(它就是 DelegatingHandler 的父類別)。這個 InnerHandler 會指向下一條管線──這裡稍微改個說法:InnerHandler 會指向下一層管線。所以,你應該可以想像得到,就像拆禮物時,外包裝盒打開,裡面還有個盒子,再打開,裡面又還有個盒子…如此層層串接,各層之間的聯繫就是透過 InnerHandler 屬性。你也可以把它想成俄羅斯娃娃的結構,如果你知道這玩意的話。
俄羅斯娃娃(取自 wikipedia.org) |
OK! 文字說明的部分差不多夠了,實作看看吧。
撰寫訊息處理器
之前遇過幾次,撰寫用戶端應用程式的開發人員一直收不到正確結果,老懷疑是 Web API 有 bug 而不肯檢視自己發出的請求內容是否有問題。最後只得將 HTTP 請求的詳細內容全部寫入 log 檔,作為證據。這裡就用一個簡單的訊息處理器來示範如何記錄 HTTP 請求的內容,程式碼是參考自 WebAPIContrib 裡面的 LoggingHandler,只是簡化了些 。
我們的自訂訊息處理器要繼承自前面提過的 DelegatingHandler。我先建立一個 ASP.NET MVC 4 應用程式專案:MessageHandlerDemo。接著在專案的根目錄下建一個 Handlers 子目錄,用來放自己寫的訊息處理器:RequestLogHandler。
建立 RequestLogHandler 類別之後,令它繼承自 DelegatingHandler,然後改寫(override)SendAsync 方法。程式碼如下:
using System; using System.Web; using System.Net.Http; namespace MessageHandlerDemo.Handlers { public class RequestLogHandler : DelegatingHandler { protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { LogRequest(request); return base.SendAsync(request, cancellationToken); } private void LogRequest(HttpRequestMessage request) { var info = new RequestLogInfo { HttpMethod = request.Method.Method, UriAccessed = request.RequestUri.AbsoluteUri, IPAddress = HttpContext.Current != null ? HttpContext.Current.Request.UserHostAddress : "0.0.0.0", }; if (request.Content != null) { request.Content.ReadAsByteArrayAsync() .ContinueWith((task) => { info.BodyContent = System.Text.UTF8Encoding.UTF8.GetString(task.Result); }); } // Serialize to JSON string. string json = Newtonsoft.Json.JsonConvert.SerializeObject(info); string uniqueid = DateTime.Now.Ticks.ToString(); string logfile = String.Format("C:\\Temp\\{0}.txt", uniqueid); System.IO.File.WriteAllText(logfile, json); } } public class RequestLogInfo { //public List<string> Header { get; set; } public string HttpMethod { get; set; } public string UriAccessed { get; set; } public string IPAddress { get; set; } public string BodyContent { get; set; } } }
註冊訊息處理器
訊息處理器寫好之後,還得註冊,才會起作用。我將註冊的動作寫在 App_Start\WebApiConfig.cs 裡面。參考下列程式碼:
using System; using System.Web.Http; namespace MessageHandlerDemo { public static class WebApiConfig { public static void Register(HttpConfiguration config) { // ...略... config.MessageHandlers.Add(new Handlers.RequestLogHandler()); } } }
執行此範例程式時,每當用戶端發出請求,C:\Temp 目錄下就會產生對應的 log 檔案。檔案內容是 JSON 格式的字串。
最後附上一張類別圖:
小結
- 對於 HTTP 請求與回應,ASP.NET Web API 對應的實作分別是 HttpRequestMessage 與 HttpResponseMessage。
- HttpSever 本身也是一個訊息處理器(繼承自 DelegatingHandler),它的責任是分派 HttpRequestMessage,以及建立 HttpResponseMessage。
- 撰寫自訂訊息處理器的步驟:建立一個新類別,令它繼承自 DelegatingHandler,並改寫(override)SendAsync 方法,然後將它加入(註冊)到 Web API 處理管線中。
- DelegatingHandler 繼承自抽象類別 HttpMessageHandler。
- DelegatingHandler 有個 InnerHandler 屬性,型別為 HttpMessageHandler,用來指向下一個處理器。管線(或俄羅斯娃娃)的訊息處理結構即由此屬性串接而成。
文中的範例係修剪自 WebAPIContrib 裡面的 LoggingHandler。WebAPIContrib 裡面還提供了數個現成的訊息處理器以及其他輔助工具,可節省我們不少時間,也是不錯的學習材料。
參考資料
沒有留言: