Web API中的消息处理程序(Message Handler)

一、消息处理程序的概念

  信息处理程序(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可以:

读取或修改请求标头。
为响应添加响应标头。
在请求到达控制器之前验证请求。

此图显示了插入管道的两个自定义处理程序:

 Web API中的消息处理程序(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,如下图所示:

Web API中的消息处理程序(Message Handler)

这时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)

这里总结了Web API中的Message Handler中的概念和基本用法,了解更多用法可以查看官网