一、消息处理程序的概念
信息处理程序(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中的概念和基本用法,了解更多用法可以查看官网。