1. Http Message Handler
WebApi中的MessageHandler类似MVC中的filter,可用于请求/响应到达真正目标前对请求或者响应进行修改,比如:用户身份验证,请求头修改,返回数据的修改等。
先看一张webapi的请求流程图:
{Request} } Pipeline 流向从上到下
|= HttpServer
|= { DelegatingHandler } - 这里是个集合,可能存在0-n 个messagehandler
|= Inner Message Handler - 内置的一些消息处理
|= HttpRoutingDispatcher
|= HttpControllerDispatcher
|= Controller
| Action
{ Response } } end
从上面webapi:Http message lifecyle简图我们可以看出,在存在DelegatingHandler情况下,任何请求/响应在到达真正的目标前,都要先被DelegatingHandler处理,也就意味着DelegatingHandler拥有了决定继续响应请求和修改返回响应结果的权利.这里的delegatingHandler就是我们的messagehandler。
如果你了解nodejs express框架,你会发现messagehandler跟express框架的中间件在概念上基本是一样的。
通过自定义MessageHandler我们可以做很多针对请求/响应定制性的东西.
2. 自定义Message handler
自定义Messsage handler有两种途径,一种是直接通过继承HttpMessageHandler实现抽象方法SendAsync,另一种是通过继承DelegatingHandler重载SendAysnc方法实现.
(DelegatingHandler本身就是派生与HttpMesssageHandler,实现了SendAsync抽象方法,DelegatingHandlerh和HttpMessageHandler都是abstract类)
public abstract DelegatingHandler: HttpMessageHandler{ //HttpMessageHandler{ [abstrct][method] sendAsync() }
//some some
//sendasync
protected Task<HttpResponseMessage> SendAsync(....){
//some
//一些内置的操作
return this.innerHandler.SendAsync(...);
}
//innermessagehandle
private HttpMessageHandler innerHandler ;
}
实现了自定义的message handler,同时还需要添加到管线中消息处理器才能起作用,此时的消息处理器是全局的会被应用到所有的请求/响应上,
注册消息处理器可以通过HttpConfiguration::MessageHandlers.Add( 自定义消息处理器 ) .
a.一个简单的消息处理器. 实现了两个操作:
1) 如果请求消息头中没有包含X-TOKEN标识,那么直接返回NotFound,终止消息的继续传递
2) 针对响应消息,在响应消息头添加标识X-CUSTOM-HEADER
public class CustomTokenValidMessageHandler: DelegatingHandler
{
private readonly string X-TOKEN = "X-TOKEN";
//override sendAsync
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request , CancellationToken token )
{
//判断请求头中是否存在X-TOKEN标识以及值
//不存在情况下直接返回notfound,终止请求继续向下传递
if( !request.Headers.Contains(X-TOKEN) ){
var res = new HttpResponseMessage(HttpStatusCode.NotFound);
//这里使用TaskCompletionSource,可以通过setresult手动设置结果
var ts = new TaskCompletionSource<HttpResponseMessage>()
ts.SetResult( res );
return ts.Task;
}
//2:t通过修改响应头,添加一个标识X-CUSTOM-HEADER
var response = base.SendAsync(request,token);
response.Result.Headers.Add("X-CUSTOM-HEADER","VIsonme-19-fz");
return response;
}
}
//注册Message handler
b. MS官方的一个很好API验证的例子,在现实场景中都是常见的,比如某应用调用第三方WEB API,API方平台都会要求应用拥有一个key或者token.
public class APIKeyValidMessageHandler: DelegatingHandler
{
private string _app_api_key = string.Empty;
public APIKeyValidMessageHandler(string apikey){_app_api_key = apikey;}
private bool CheckApiKey( HttpRequestMessage req )
{
//解析查询字符串
var query_string = req.RequestUri.ParseQueryString();
var k = query_string["key"];
return (k == this._app_api_key);
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request , CancellationToken token )
{
if( !this.CheckApiKey( request ))
{
//key无效情况
var res = new HttpResponseMessage(HttpStatusCode.Forbidden);
var ts = new TaskCompletionSource<HttpResponseMessage>()
ts.SetResult( res );
return ts.Task;
}
return base.SendAsync(request , token);
}
}
//注册message handler
3.给指定的路由添加Message handler.
上面方式注册的消息处理器都是全局的,针对所有请求/响应,在某些场景下我们可能并不想消息处理器全局应用而是只针对某些路由,比如:某些API的访问我只希望一些核心的action需要被验证而其他的接口可以被大众化访问,这个时候就没有必要将身份验证的消息处理器应用到全局了 .
针对某些路由注册Message Handler,可以在HttpConfiguration::Routes::MapHttpRoute中给handler字段指定仅仅在该route上被处理的Message handler.
==> WebApiConfig.cs ==> Register [method]
config.Routes.MapHttpRoute(
name:"route name",
routeTemplate:"api/{controller}/{action}/{id},
default: new{id=RouteParameter.Optional},
constraints:null,
handler: new APIKeyValidMessageHandler("V-198-fz")
);
//上面我们指定了handler为APIKeyValidMessageHandler
//那么APIKeyValidMessageHandler仅仅作用在该路由上,而不会
//被应用到全局
//如果希望全局应用只要在config.MessageHandlers.Add(your mh)
//就可以了
这个时候Http message lifecyle简图就变成了:
{ Request }
| = HttpServer
| = Message Handler
| = RouteDispathcer
| ----- 》----------------------{route 1 } ------------
|= Defaut route |
|-- Controller dispatcher |= custom messge handler
|--------------------------------|------------------> { Response }
通过上面我们将messagehandler应用到指定的路由但是从简图我们看出,这个时候我们在route1看不到controller dispathcer了,controller diapathcer被我们自定义的message handler给取代了。
这种情况请不是我们所期望的,我们期望应该是将message hander插入到指定的route中,但是并不应该影响到我们原先route中应该的逻辑,也就是正确的应该是
route1 : 流程应该是这样的
{ custom message handler }
| Controller distatcher
| controller
{ response }
我们需要重新配置我们的message handler,通过HttpClientFactory创建一个路由管道点
DelegatingHandler[] handlers = new DelegatingHandler[]{ new APIKeyValidMessageHandler() }; var routeHandlers = HttpClientFactory.CreatePipeline( new HttpControllerDispatcher(config),handler); //MAP HTTP ROUTE config.Routes.MapHttpRoute( name:"route1", routeTemplate:"api/{controller}/{id}", defaults: /// constraints: null, handlers:routeHandlers );