HttpClient 管道黑科技,让你的请求效率飞起来!🚀
Pipeline(管道)是HttpClient网络请求的隐形加速器
。
HttpClient 采用了与 ASP.NET Core 管道机制相同的设计,通过组合 HttpMessageHandler
和 中间件模式
形成请求/响应链来实现。允许你在请求和响应之间插入多个处理步骤,这些步骤可以按顺序执行,类似于管道。
-
提高性能
- 异步流水线式处理:利用async/await串联管道,实现非阻塞的请求-响应流水线,提升吞吐量;
- 资源高效使用:结合IHttpClientFactory自动管理连接池,高效使用HttpClient实例;
-
优化程序设计
- 职责分离:将请求处理拆分为多个独立管道(如认证、日志、重试),每个管道专注单一功能;
- 可插拔性:通过增减管道,动态调整处理流程(如临时添加请求加密步骤),无需修改核心逻辑;
- 统一扩展点:为整个请求流程,形成统一的扩展点;结合AOP模式,更容易实现统一日志、权限、拦截、自定义流程等扩展功能。
-
增加可维护
- 基于管道,形成模块化、可扩展化程序设计,提高可维护性;
- 调试友好:每个管道可单独测试、跟踪;
初始化
#!import "./Ini.ipynb"
1、创建自定义 HttpMessageHandler
/// <summary>
/// 日志管道(中间件)
/// </summary>
public class LoggingHandler : DelegatingHandler
{
public LoggingHandler()
{
//防止成为最后一个管道时异常
this.InnerHandler = new SocketsHttpHandler();
}
protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
{
Console.WriteLine("日志管道: LoggingHandler -> Send -> Before");
HttpResponseMessage response = base.Send(request, cancellationToken);
response.EnsureSuccessStatusCode();
Console.WriteLine("日志管道: LoggingHandler -> Send -> After");
return response;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// 记录请求信息
Console.WriteLine($"日志管道: LoggingHandler -> Send -> Before Request: {request.Method} {request.RequestUri}");
// 调用管道中的下一个处理器,并获取响应
var response = await base.SendAsync(request, cancellationToken);
// 记录响应信息
Console.WriteLine($"日志管道: LoggingHandler -> Send -> After Response: {response.StatusCode}");
return response;
}
}
/// <summary>
/// 令牌验证管道(中间件)
/// </summary>
public class TokenDelegatingHandler : DelegatingHandler
{
public TokenDelegatingHandler()
{
//防止成为最后一个管道时异常
this.InnerHandler = new SocketsHttpHandler();
}
protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
{
Console.WriteLine("令牌验证管道: TokenDelegatingHandler -> Send -> Added Token");
if (!request.Headers.Contains(Microsoft.Net.Http.Headers.HeaderNames.Authorization))
{
Console.WriteLine("没有 Token, TokenDelegatingHandler 添加之");
request.Headers.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, "Bearer " + "a.b.c");
}
else
{
Console.WriteLine($"已有Token, {request.Headers.Authorization}");
}
HttpResponseMessage response = base.Send(request, cancellationToken);
Console.WriteLine("令牌验证管道: TokenDelegatingHandler -> Send -> After");
return response;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Console.WriteLine("令牌验证管道: TokenDelegatingHandler -> SendAsync -> Before");
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
Console.WriteLine("令牌验证管道: TokenDelegatingHandler -> SendAsync -> After");
return response;
}
}
///<summary>
/// 异常处理管道(中间件)
/// </summary>
public class ExceptionDelegatingHandler : DelegatingHandler
{
public ExceptionDelegatingHandler()
{
//防止成为最后一个管道时异常
this.InnerHandler = new SocketsHttpHandler();
}
protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
{
Console.WriteLine("异常处理管道: ExceptionDelegatingHandler -> Send -> Before");
HttpResponseMessage response;
try
{
response = base.Send(request, cancellationToken);
response.EnsureSuccessStatusCode();
}
catch(Exception ex)
{
Console.WriteLine($"异常管道中间件,监控到异常:{ex.Message}");
response = new HttpResponseMessage(HttpStatusCode.ExpectationFailed);
}
finally
{
//清理业务
}
Console.WriteLine("异常处理管道: ExceptionDelegatingHandler -> Send -> After");
return response;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Console.WriteLine("异常处理管道: ExceptionDelegatingHandler -> SendAsync -> Before");
HttpResponseMessage response;
try
{
response = response = await base.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
}
catch(Exception ex)
{
Console.WriteLine($"异常管道中间件,监控到异常:{ex.Message}");
response = new HttpResponseMessage(HttpStatusCode.ExpectationFailed);
}
finally
{
//清理业务
}
Console.WriteLine("异常处理管道: ExceptionDelegatingHandler -> SendAsync -> After");
return response;
}
}
2、组合多个 HttpMessageHandler 形成请求/响应管道(处理链)
组合管道,就是利用 DelegatingHandler 类的 InnerHandler 属性,指定本管道的下一个管道(中间件),将多个管道链接起来,形成一个请求/响应
处理链。
- 默认管道
{ //默认配置
var httpClient = new HttpClient()
{
BaseAddress = webApiBaseUri
};
//发送请求,从日志看管道执行顺序
var response = await httpClient.GetAsync("/api/Hello/Ping");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine($"响应内容:{content}");
}
- 手动组合
{ //组装管道
/*
1. 最后一个管道必须是SocketsHttpHandler 或者 保证最后的管道的InnerHandler有默认值
2. 倒序组装,正序执行:HttpClient构造函数,传入的HttpMessageHandler是第一个执行的,其InnerHandler是下一个执行的,依次类推
*/
var lastHandler = new SocketsHttpHandler()
{
UseCookies = false,
UseProxy = false,
};
var logPipeline = new LoggingHandler()
{
InnerHandler = lastHandler
};
var exceptionHandler = new ExceptionDelegatingHandler()
{
InnerHandler = logPipeline
};
var tokenHandler = new TokenDelegatingHandler()
{
InnerHandler = exceptionHandler
};
var httpClient = new HttpClient(tokenHandler)
{
BaseAddress = webApiBaseUri
};
//发送请求,从日志看管道执行顺序
var response = await httpClient.GetAsync("/api/Config/GetApiConfig");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine($"响应内容:{content}");
}
- 动态组合
整个管道链的组装,是非常灵活的,动态组装也不是难事!
/// <summary>
/// 管道构建器
/// </summary>
public class HttpMessageHandlerBuilder
{
public List<DelegatingHandler> Handlers { get; set; }
public HttpMessageHandlerBuilder()
{
this.Handlers = new List<DelegatingHandler>();
}
public HttpMessageHandlerBuilder(List<DelegatingHandler> handlers):base()
{
if(handlers != null)
{
this.Handlers = handlers;
}
}
public void AddHttpMessageHandler(DelegatingHandler handler)
{
Handlers.Add(handler);
}
public void RemoveHttpMessageHandler(DelegatingHandler handler)
{
Handlers.Remove(handler);
}
public List<DelegatingHandler> FindHttpMessageHandler(DelegatingHandler handler)
{
return Handlers.Where(h => h.GetType() == handler.GetType()).ToList();
}
/// <summary>
/// 根据管道配置:创建一个新HttpClient实例
/// </summary>
public HttpClient Build()
{
if(this.Handlers ==null || this.Handlers.Count == 0)
{
return new HttpClient();
}
//默认最终处理器
HttpMessageHandler defaultLastHandler = new SocketsHttpHandler()
{
//自定义配置
PooledConnectionLifetime = TimeSpan.FromSeconds(120),
};
//倒序组装:完成后执行顺序与注册顺序一致
//拷贝份,省得多次调用有问题
HttpMessageHandler currentHandler = new SocketsHttpHandler()
{
//自定义配置
PooledConnectionLifetime = TimeSpan.FromSeconds(120),
};
var builderHandlers = this.Handlers.ToList<DelegatingHandler>();
builderHandlers.Reverse();
if(builderHandlers[0].InnerHandler != null)
{
currentHandler = builderHandlers[0].InnerHandler;
}
for (int i = 0; i < builderHandlers.Count; i++)
{
builderHandlers[i].InnerHandler = currentHandler;
currentHandler = builderHandlers[i];
}
// 利用管道创建HttpClient
var httpClient = new HttpClient(currentHandler);
return httpClient;
}
}
//构建管道
List<DelegatingHandler> handlers = new List<DelegatingHandler>()
{
new TokenDelegatingHandler(),
};
//添加
handlers.Add(new LoggingHandler());
handlers.Add(new ExceptionDelegatingHandler());
var handlerBuilder = new HttpMessageHandlerBuilder(handlers);
//第一次:发送请求,从日志看管道执行顺序
var httpClient = handlerBuilder.Build();
httpClient.BaseAddress = webApiBaseUri;
webApiBaseUrl.Display();
var response = await httpClient.GetAsync("/api/Config/GetApiConfig");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine($"响应内容:{content}");
3、发送请求
和一般的请求,没什么区别。
4、管道执行顺序
- 请求从最外层的 DelegatingHandler 开始,逐层进入内部处理器。
- 响应则从最内层返回,经过每一层的 SendAsync 方法后返回给调用者。
执行下面示例,查看日志输出:
{
//组装管道
//最后一个管道,必须是SocketsHttpHandler,或者默认的是SocketsHttpHandler
var lastHandler = new SocketsHttpHandler()
{
UseCookies = false,
UseProxy = false,
};
var logPipeline = new LoggingHandler()
{
InnerHandler = lastHandler
};
var exceptionHandler = new ExceptionDelegatingHandler()
{
InnerHandler = logPipeline
};
var tokenHandler = new TokenDelegatingHandler()
{
InnerHandler = exceptionHandler
};
var httpClient = new HttpClient(tokenHandler)
{
BaseAddress = webApiBaseUri
};
//发送请求,从日志看管道执行顺序
var response = await httpClient.GetAsync("/api/Hello/Ping");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine($"响应内容:{content}");
}
5、使用 IHttpClientFactory 构建更复杂的管道(推荐)
小结
特性 | 描述 |
---|---|
HttpMessageHandler | 是 HttpClient 的核心处理单元:ml-citation{ref=“1” data=“citationList”} |
DelegatingHandler | 可用于构建自定义中间件逻辑:ml-citation{ref=“1,5” data=“citationList”} |
管道顺序 | 请求按顺序进入,响应逆序返回:ml-citation{ref=“5,8” data=“citationList”} |
IHttpClientFactory | 推荐用于生产环境,支持灵活配置:ml-citation{ref=“1,7” data=“citationList”} |
通过添加更多 DelegatingHandler 实现缓存、重试、认证等!