一、消息處理程序的概念
信息處理程序(Message Handler)接收HTTP請求並返回一個HTTP響應的類。Message Handler繼承 HttpMessageHandler 類。
通常,一系列消息處理程序協同工作。第一個Message Handler接收HTTP請求,進行一些處理,並將請求提供給下一個Message Handler。在某些時候,響應被創建並返回到Message Handler,此模式稱為委托處理程序(delegating handler)。
二、服務器端消息處理程序
在服務器端,Web API管道使用一些內置的消息處理程序:
HttpServer 從主機獲取請求。
HttpRoutingDispatcher 根據請求進行路由。
HttpControllerDispatcher 將請求發送到Web API控制器。
我們可以向管道添加自定義處理程序。消息處理程序更適合處理Http messages(比起控制器)。因為Message Handler可以:
讀取或修改請求標頭。
為響應添加響應標頭。
在請求到達控制器之前驗證請求。
此圖顯示了插入管道的兩個自定義處理程序:
三、自定義Message Handler
第一步:創建Message Handler
要編寫自定義消息處理程序,請從 System.Net.Http.DelegatingHandler 派生並覆蓋 SendAsync 方法。此方法具有以下簽名:
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
該方法將HttpRequestMessage作為輸入,並異步返回HttpResponseMessage。典型的實現執行以下操作:
1.處理請求消息。 2.調用base.SendAsync將請求發送到內部處理程序。 3.內部處理程序返回響應消息。(此步驟是異步的。) 4.處理響應並將其返回給調用者。
一個簡單的栗子:
public class MessageHandler1 : DelegatingHandler { protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { Debug.WriteLine("Process request"); // Call the inner handler. var response = await base.SendAsync(request, cancellationToken); Debug.WriteLine("Process response"); return response; } }
調用 base.SendAsync 是異步的。如果處理程序在此調用后執行任何操作,請使用await關鍵字。
委托處理程序也可以跳過inner handler
一個跳過inner handler的栗子:
public class MessageHandler2 : DelegatingHandler { protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { // Create the response. var response = new HttpResponseMessage(HttpStatusCode.OK){ Content = new StringContent("Hello!")}; // Note: TaskCompletionSource創建一個不包含委托的task var tsc = new TaskCompletionSource<HttpResponseMessage>(); tsc.SetResult(response); // Also sets the task state to "RanToCompletion" return tsc.Task; } }
如果delegate handler在創建響應時不調用base.SendAsync,則請求將跳過管道的其余部分。這對於驗證請求的處理程序(創建錯誤響應)非常有用。
第二步:將處理程序添加到管道
要在服務器端添加消息處理程序,請將處理程序添加到 HttpConfiguration.MessageHandlers 集合中。
public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.MessageHandlers.Add(new MessageHandler1()); config.MessageHandlers.Add(new MessageHandler2()); // Other code not shown... } }
消息處理程序的調用順序與它們在MessageHandlers集合中的顯示順序相同。因為它們是嵌套的,所以響應消息反向傳播。
四、自定義消息處理程序實例
4.1 添加自定義響應標頭
這是一個Message Handler,它為每個響應消息添加一個自定義標頭:
public class CustomHeaderHandler : DelegatingHandler { async protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { HttpResponseMessage response = await base.SendAsync(request, cancellationToken); response.Headers.Add("X-Custom-Header", "This is my custom header."); return response; } }
首先,處理程序調用base.SendAsync將請求傳遞給inner handler。inner handler返回響應消息,但它使用Task <T>對象異步執行,在base.SendAsync異步完成之前,響應消息不可用。
4.2 檢查API密鑰
某些Web服務要求客戶在其請求中包含API密鑰。以下示例顯示了消息處理程序如何檢查有效API密鑰的請求:
public class ApiKeyHandler : DelegatingHandler { public string Key { get; set; } public ApiKeyHandler(string key) { this.Key = key; } //重寫sendAsyc protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (!ValidateKey(request)) { //沒有通過則創建response,code為403 var response = new HttpResponseMessage(HttpStatusCode.Forbidden); var tsc = new TaskCompletionSource<HttpResponseMessage>(); tsc.SetResult(response); return tsc.Task; } return base.SendAsync(request, cancellationToken); } //驗證密鑰 private bool ValidateKey(HttpRequestMessage message) { var query = message.RequestUri.ParseQueryString(); string key = query["key"]; return (key == Key); } }
Message Handler在URI查詢字符串中查找API密鑰。如果查詢字符串包含Key,則Message handler將請求傳遞給innner handler,沒有key的話返回403。
4.3 Per-Route Message Handler
HttpConfiguration.MessageHandlers集合中的處理程序全局應用,有時候我們的Message Handler只針對特定的路徑,如url中含有“admin”的,需要登陸后才能訪問:
public static class WebApiConfig { public static void Register(HttpConfiguration config) { //默認路由 config.Routes.MapHttpRoute( name: "Route1", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); //路由中指定Message Handler config.Routes.MapHttpRoute( name: "Route2", routeTemplate: "api2/{controller}/{id}", defaults: new { id = RouteParameter.Optional }, handler: new MessageHandler2() // per-route message handler ); //全局的Message Handler config.MessageHandlers.Add(new MessageHandler1()); // global message handler } }
在此示例中,如果請求URI與“Route2”匹配,則將分派請求MessageHandler2,如下圖所示:
這時MessageHandler2替換默認的HttpControllerDispatcher。這個栗子中MessageHandler2創建響應,匹配“Route2”的請求永遠不會轉到控制器。這使我們可以使用自己的自定義響應替換整個Web API控制器機制。
我們也可以把路由的MessageHandler委托給 HttpControllerDispatcher ,通過HttpControllerDispatcher調度到控制器。
以下代碼顯示了如何配置此路由:
// delegating handlers. DelegatingHandler[] handlers = new DelegatingHandler[] { new MessageHandler3() }; // Create a message handler chain with an end-point. var routeHandlers = HttpClientFactory.CreatePipeline( new HttpControllerDispatcher(config), handlers); config.Routes.MapHttpRoute( name: "Route2", routeTemplate: "api2/{controller}/{id}", defaults: new { id = RouteParameter.Optional }, constraints: null, handler: routeHandlers );
這里總結了Web API中的Message Handler中的概念和基本用法,了解更多用法可以查看官網。