ASP.NET Web API 2第四课——HttpClient消息处理器详解

在构建HTTP API时,我们经常需要在请求/响应管道中添加横切关注点功能:

  • 记录所有HTTP请求/响应
  • 添加认证令牌
  • 实现重试机制
  • 添加自定义头信息
  • 处理错误和异常

手动在每个API调用中添加这些逻辑会导致代码重复且难以维护。这就是HttpClient消息处理器的用武之地——它们提供了一种统一的方式来拦截和处理HTTP请求,使代码更干净、更易维护。

目录#

  1. 引言:为什么需要消息处理器?
  2. 什么是消息处理器?
  3. 自定义消息处理器的创建
    • 基本结构
    • 日志处理器示例
    • 认证处理器示例
  4. 注册和使用消息处理器
    • 全局注册
    • 特定客户端注册
  5. 消息处理器管道
  6. 常见应用场景
  7. 最佳实践
  8. 完整代码示例
  9. 总结
  10. 参考资料

什么是消息处理器?#

消息处理器(Message Handler)是ASP.NET Web API 2中处理HTTP消息的组件:

  • 继承自 DelegatingHandler 类
  • 位于 System.Net.Http 命名空间
  • 在请求发送到服务器前和响应返回客户端后进行拦截
  • 可形成处理管道(Pipeline),多个处理器按顺序执行

消息处理器管道示意图


自定义消息处理器的创建#

基本结构#

所有自定义处理器都应继承自 DelegatingHandler:

public class CustomMessageHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, 
        CancellationToken cancellationToken)
    {
        // 请求处理逻辑(发送前)
        
        // 调用下一个处理器
        var response = await base.SendAsync(request, cancellationToken);
        
        // 响应处理逻辑(接收后)
        
        return response;
    }
}

日志处理器示例#

public class LoggingHandler : DelegatingHandler
{
    private readonly ILogger _logger;
 
    public LoggingHandler(ILogger logger)
    {
        _logger = logger;
    }
 
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // 记录请求
        _logger.LogInfo($"Request: {request.Method} {request.RequestUri}");
        
        if (request.Content != null)
        {
            var requestBody = await request.Content.ReadAsStringAsync();
            _logger.LogInfo($"Request Body: {requestBody}");
        }
 
        // 调用下一个处理器
        var response = await base.SendAsync(request, cancellationToken);
        
        // 记录响应
        _logger.LogInfo($"Response Status: {response.StatusCode}");
        
        if (response.Content != null)
        {
            var responseBody = await response.Content.ReadAsStringAsync();
            _logger.LogInfo($"Response Body: {responseBody}");
        }
        
        return response;
    }
}

认证处理器示例#

public class AuthHandler : DelegatingHandler
{
    private readonly ITokenService _tokenService;
 
    public AuthHandler(ITokenService tokenService)
    {
        _tokenService = tokenService;
    }
 
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // 添加认证令牌
        var token = await _tokenService.GetTokenAsync();
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        
        // 可选:处理认证失败
        var response = await base.SendAsync(request, cancellationToken);
        
        if (response.StatusCode == HttpStatusCode.Unauthorized)
        {
            // 重新认证或刷新令牌逻辑
        }
        
        return response;
    }
}

注册和使用消息处理器#

全局注册(适用于所有HttpClient实例)#

在 WebApiConfig.cs 中注册:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // 注册消息处理器(从内向外执行)
        config.MessageHandlers.Add(new Handler1());
        config.MessageHandlers.Add(new Handler2());
        
        // 其他配置...
    }
}

特定客户端注册#

使用 HttpClientFactory 创建自定义管道的HttpClient:

var handler1 = new Handler1();
var handler2 = new Handler2();
 
// 构建处理器管道
handler1.InnerHandler = handler2;
handler2.InnerHandler = new HttpClientHandler(); // 最内层必须是HttpClientHandler
 
var client = new HttpClient(handler1);
 
// 使用client发送请求
var response = await client.GetAsync("https://api.example.com");

消息处理器管道#

处理器的执行顺序类似于中间件管道:

  1. 传入请求首先到达最外层处理器
  2. 每个处理器执行预处理逻辑
  3. 调用 base.SendAsync() 将请求传递给下一个处理器
  4. 响应沿管道反向返回
  5. 每个处理器可以处理后处理逻辑

重要规则:

  • 必须调用 base.SendAsync() 来继续管道
  • 最内层处理器必须是 HttpClientHandler
  • 处理器按注册顺序执行(全局注册)
  • 管道方向:第一个注册的处理器最先处理请求,最后处理响应

常见应用场景#

场景实现方式
请求/响应日志在SendAsync前后记录内容
认证令牌管理自动添加Authorization头,处理401响应刷新令牌
请求重试机制捕获特定异常并重试请求
API版本控制添加自定义头(如X-Api-Version: 2.0)
限速和节流使用令牌桶算法限制请求频率
错误统一处理转换错误响应为标准化错误对象
请求签名为安全请求添加HMAC签名
缓存控制检查缓存头,返回304响应

最佳实践#

  1. 保持无状态

    • 处理器应设计为无状态,便于重用和测试
    • 通过构造函数注入依赖项
  2. 关注点分离

    • 每个处理器只负责一个功能(单一职责原则)
    • 避免创建"上帝处理器"
  3. 异步操作

    • 始终使用async/await进行异步操作
    • 避免使用 .Result 或 .Wait() 阻塞线程
  4. 异常处理

    • 在处理器内捕获并处理特定异常
    • 使用HttpRequestException包装自定义异常
  5. 性能考虑

    • 避免在处理器中执行CPU密集型操作
    • 缓存需要多次使用的内容(如身份令牌)
  6. 管道顺序

    • 认证处理器应在最外层
    • 日志处理器应在管道入口
    • 重试处理器应在认证处理器之后
  7. 测试

    • 使用HttpMessageHandler模拟请求
    • 单独测试每个处理器功能

完整代码示例#

组合认证和日志处理器#

// 配置处理器管道
var logger = new ConsoleLogger();
var tokenService = new TokenService();
 
var authHandler = new AuthHandler(tokenService)
{
    InnerHandler = new LoggingHandler(logger)
};
 
var httpClient = new HttpClient(authHandler);
 
// 使用示例
try
{
    var response = await httpClient.GetAsync("https://api.example.com/data");
    response.EnsureSuccessStatusCode();
    var content = await response.Content.ReadAsStringAsync();
    Console.WriteLine(content);
}
catch (HttpRequestException ex)
{
    Console.WriteLine($"请求失败: {ex.Message}");
}

TokenService实现#

public class TokenService : ITokenService
{
    private string _cachedToken;
    private DateTime _tokenExpiry = DateTime.MinValue;
    
    public async Task<string> GetTokenAsync()
    {
        if (DateTime.UtcNow < _tokenExpiry.AddMinutes(-5))
            return _cachedToken;
        
        // 获取新令牌的逻辑
        using var client = new HttpClient();
        var response = await client.PostAsync("https://auth.example.com/token", ...);
        var result = await response.Content.ReadAsAsync<TokenResult>();
        
        _cachedToken = result.AccessToken;
        _tokenExpiry = DateTime.UtcNow.AddSeconds(result.ExpiresIn);
        
        return _cachedToken;
    }
}

总结#

HttpClient消息处理器是ASP.NET Web API中强大的抽象层:

  1. 提供请求/响应管道的拦截能力
  2. 实现跨切面关注点的重用
  3. 支持灵活的处理管道配置
  4. 遵循开闭原则,无需修改现有代码即可添加新功能

通过合理使用消息处理器,您可以:

  • 减少代码重复
  • 提高可维护性
  • 统一处理策略
  • 简化客户端代码

建议从简单的日志处理器开始实践,逐步添加认证、重试等高级功能,构建健壮的HTTP客户端架构。


参考资料#

  1. 官方文档:HTTP 消息处理器
  2. 自定义消息处理器示例项目
  3. Polly库(高级弹性处理)
  4. HTTP Client Factory最佳实践
  5. 设计模式:责任链模式