ASP.NET Web API中的 HTTP 消息处理程序
消息处理程序是接收 HTTP 请求并返回 HTTP 响应的类。 消息处理程序派生自抽象的 HttpMessageHandler 类。
通常,一系列消息处理程序链接在一起。 第一个处理程序接收 HTTP 请求,执行一些处理,并将请求提供给下一个处理程序。 在某个时候,会创建响应并返回链。 此模式称为 委派 处理程序。
Server-Side消息处理程序
在服务器端,Web API 管道使用一些内置消息处理程序:
- HttpServer 从主机获取请求。
- HttpRoutingDispatcher 根据路由调度请求。
- HttpControllerDispatcher 将请求发送到 Web API 控制器。
可以将自定义处理程序添加到管道。 消息处理程序适用于在 HTTP 消息 (级别运行的交叉问题,而不是) 控制器操作。 例如,消息处理程序可能:
- 读取或修改请求标头。
- 向响应添加响应标头。
- 在请求到达控制器之前对其进行验证。
此图显示了插入管道中的两个自定义处理程序:
注意
在客户端,HttpClient 还使用消息处理程序。 有关详细信息,请参阅 HttpClient 消息处理程序。
自定义消息处理程序
若要编写自定义消息处理程序,请从 System.Net.Http.DelegatingHandler 派生并重写 SendAsync 方法。 此方法具有以下签名:
Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken);
方法采用 HttpRequestMessage 作为输入,并异步返回 HttpResponseMessage。 典型的实现将执行以下操作:
- 处理请求消息。
- 调用
base.SendAsync
以将请求发送到内部处理程序。 - 内部处理程序返回响应消息。 (此步骤是异步的。)
- 处理响应并将其返回到调用方。
下面是一个简单示例:
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 关键字 (keyword) ,如下所示。
委托处理程序还可以跳过内部处理程序并直接创建响应:
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 creates a task that does not contain a delegate.
var tsc = new TaskCompletionSource<HttpResponseMessage>();
tsc.SetResult(response); // Also sets the task state to "RanToCompletion"
return tsc.Task;
}
}
如果委托处理程序在不调用 base.SendAsync
的情况下创建响应,则请求将跳过管道的其余部分。 这对于验证请求 (创建错误响应) 的处理程序非常有用。
将处理程序添加到管道
若要在服务器端添加消息处理程序,请将处理程序添加到 HttpConfiguration.MessageHandlers 集合。 如果使用“ASP.NET MVC 4 Web 应用程序”模板创建项目,则可以在 WebApiConfig 类中执行此操作:
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 集合中的出现顺序相同。 因为它们是嵌套的,因此响应消息会向另一个方向传播。 也就是说,最后一个处理程序是第一个获取响应消息的处理程序。
请注意,无需设置内部处理程序;Web API 框架会自动连接消息处理程序。
如果是 自承载,请创建 HttpSelfHostConfiguration 类的实例,并将处理程序添加到 MessageHandlers 集合。
var config = new HttpSelfHostConfiguration("http://localhost");
config.MessageHandlers.Add(new MessageHandler1());
config.MessageHandlers.Add(new MessageHandler2());
现在,我们来看看自定义消息处理程序的一些示例。
示例:X-HTTP-Method-Override
X-HTTP-Method-Override 是一个非标准 HTTP 标头。 它专为无法发送某些 HTTP 请求类型(如 PUT 或 DELETE)的客户端而设计。 相反,客户端会发送一个 POST 请求,并将 X-HTTP-Method-Override 标头设置为所需的方法。 例如:
X-HTTP-Method-Override: PUT
下面是一个消息处理程序,它添加了对 X-HTTP-Method-Override 的支持:
public class MethodOverrideHandler : DelegatingHandler
{
readonly string[] _methods = { "DELETE", "HEAD", "PUT" };
const string _header = "X-HTTP-Method-Override";
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
// Check for HTTP POST with the X-HTTP-Method-Override header.
if (request.Method == HttpMethod.Post && request.Headers.Contains(_header))
{
// Check if the header value is in our methods list.
var method = request.Headers.GetValues(_header).FirstOrDefault();
if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase))
{
// Change the request method.
request.Method = new HttpMethod(method);
}
}
return base.SendAsync(request, cancellationToken);
}
}
在 SendAsync 方法中,处理程序检查请求消息是否为 POST 请求,以及它是否包含 X-HTTP-Method-Override 标头。 如果是这样,它将验证标头值,然后修改请求方法。 最后,处理程序调用 base.SendAsync
将消息传递给下一个处理程序。
当请求到达 HttpControllerDispatcher 类时, HttpControllerDispatcher 将根据更新的请求方法路由请求。
示例:添加自定义响应标头
下面是一个消息处理程序,它将自定义标头添加到每个响应消息:
// .Net 4.5
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
以将请求传递给内部消息处理程序。 内部处理程序返回响应消息,但它使用 Task<T> 对象异步执行此操作。 响应消息在异步完成之前 base.SendAsync
不可用。
此示例使用 await 关键字 (keyword) 在完成后异步SendAsync
执行工作。 如果面向 4.0 .NET Framework,请使用任务<T>。ContinueWith 方法:
public class CustomHeaderHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken).ContinueWith(
(task) =>
{
HttpResponseMessage response = task.Result;
response.Headers.Add("X-Custom-Header", "This is my custom header.");
return response;
}
);
}
}
示例:检查 API 密钥
某些 Web 服务要求客户端在其请求中包含 API 密钥。 以下示例演示消息处理程序如何检查有效 API 密钥的请求:
public class ApiKeyHandler : DelegatingHandler
{
public string Key { get; set; }
public ApiKeyHandler(string key)
{
this.Key = key;
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!ValidateKey(request))
{
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);
}
}
此处理程序在 URI 查询字符串中查找 API 密钥。 (对于此示例,我们假定密钥是静态字符串。实际实现可能会使用更复杂的 validation.) 如果查询字符串包含 键,处理程序会将请求传递给内部处理程序。
如果请求没有有效的密钥,处理程序会创建状态为 403(禁止)的响应消息。 在这种情况下,处理程序不调用 base.SendAsync
,因此内部处理程序从不接收请求,控制器也不会接收请求。 因此,控制器可以假定所有传入请求都具有有效的 API 密钥。
注意
如果 API 密钥仅适用于某些控制器操作,请考虑使用操作筛选器而不是消息处理程序。 执行 URI 路由后运行操作筛选器。
Per-Route消息处理程序
HttpConfiguration.MessageHandlers 集合中的处理程序全局应用。
或者,在定义路由时,可以将消息处理程序添加到特定路由:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "Route1",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "Route2",
routeTemplate: "api2/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: null,
handler: new MessageHandler2() // per-route message handler
);
config.MessageHandlers.Add(new MessageHandler1()); // global message handler
}
}
在此示例中,如果请求 URI 与“Route2”匹配,则会将请求调度到 MessageHandler2
。 下图显示了这两个路由的管道:
请注意, MessageHandler2
将替换默认的 HttpControllerDispatcher。 在此示例中, MessageHandler2
创建响应,并且与“Route2”匹配的请求永远不会转到控制器。 这样,便可以将整个 Web API 控制器机制替换为自己的自定义终结点。
或者,按路由的消息处理程序可以委托给 HttpControllerDispatcher,后者然后调度到控制器。
以下代码演示如何配置此路由:
// List of 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
);