You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
HttpClientStudy/Docs/1.4.2.高级使用.使用管道.ipynb

711 lines
24 KiB
Plaintext

1 month ago
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"source": [
"# HttpClient 管道黑科技,让你的请求效率飞起来!🚀"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Pipeline(管道)是HttpClient网络请求的`隐形加速器`。\n",
"\n",
"HttpClient 采用了与 ASP.NET Core 管道机制相同的设计,通过组合 `HttpMessageHandler` 和 `中间件模式` 形成请求/响应链来实现。允许你在请求和响应之间插入多个处理步骤,这些步骤可以按顺序执行,类似于管道。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- 提高性能\n",
" + 异步流水线式处理利用async/await串联管道实现非阻塞的请求-响应流水线,提升吞吐量;\n",
" + 资源高效使用结合IHttpClientFactory自动管理连接池高效使用HttpClient实例\n",
"\n",
"- 优化程序设计\n",
" + 职责分离:将请求处理拆分为多个独立管道(如认证、日志、重试),每个管道专注单一功能;\n",
" + 可插拔性‌:通过增减管道,动态调整处理流程(如临时添加请求加密步骤),无需修改核心逻辑;\n",
" + 统一扩展点为整个请求流程形成统一的扩展点结合AOP模式更容易实现统一日志、权限、拦截、自定义流程等扩展功能。\n",
"\n",
"- 增加可维护\n",
" + 基于管道,形成模块化、可扩展化程序设计,提高可维护性;\n",
" + 调试友好:每个管道可单独测试、跟踪;"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 初始化"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [
{
"data": {
"text/markdown": [
"## 初始化\n",
"这是全局共用文件包括Nuget包引用、全局类库引用、全局文件引用、全局命名空间引用、全局变量、全局方法、全局类定义等功能。\n",
"\n",
"在业务笔记中引用,执行其它单元格之前先执行一次。"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<div><div></div><div></div><div><strong>Installed Packages</strong><ul><li><span>Microsoft.Extensions.DependencyInjection, 9.0.5</span></li><li><span>Microsoft.Extensions.Http, 9.0.5</span></li><li><span>Microsoft.Extensions.Http.Polly, 9.0.5</span></li><li><span>Microsoft.Extensions.Logging, 9.0.5</span></li><li><span>Microsoft.Extensions.Logging.Console, 9.0.5</span></li><li><span>Microsoft.Net.Http.Headers, 9.0.5</span></li><li><span>Polly, 8.5.2</span></li><li><span>Refit, 8.0.0</span></li><li><span>Refit.HttpClientFactory, 8.0.0</span></li><li><span>System.Net.Http.Json, 9.0.5</span></li></ul></div></div>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"配置文件根目录c:\\Users\\ruyu\\Desktop\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n",
"配置文件根目录c:\\Users\\ruyu\\Desktop\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n",
"启动WebApi项目...\n",
"程序[c:\\Users\\ruyu\\Desktop\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.WebApp\\HttpClientStudy.WebApp.exe]已在新的命令行窗口执行。如果未出现新命令行窗口,可能是程序错误造成窗口闪现!\n",
"已启动WebApi项目,保持窗口打开状态!\n",
"初始化完成!\n"
]
}
],
"source": [
"#!import \"./Ini.ipynb\""
]
},
{
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"source": [
"## 1、创建自定义 HttpMessageHandler"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"/// <summary>\n",
"/// 日志管道(中间件)\n",
"/// </summary>\n",
"public class LoggingHandler : DelegatingHandler\n",
"{\n",
" public LoggingHandler()\n",
" {\n",
" //防止成为最后一个管道时异常\n",
" this.InnerHandler = new SocketsHttpHandler();\n",
" }\n",
" \n",
" protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)\n",
" {\n",
" Console.WriteLine(\"日志管道: LoggingHandler -> Send -> Before\");\n",
"\n",
" HttpResponseMessage response = base.Send(request, cancellationToken);\n",
" response.EnsureSuccessStatusCode();\n",
"\n",
" Console.WriteLine(\"日志管道: LoggingHandler -> Send -> After\");\n",
"\n",
" return response;\n",
" }\n",
"\n",
" protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\n",
" {\n",
" // 记录请求信息\n",
" Console.WriteLine($\"日志管道: LoggingHandler -> Send -> Before Request: {request.Method} {request.RequestUri}\");\n",
" // 调用管道中的下一个处理器,并获取响应\n",
" var response = await base.SendAsync(request, cancellationToken);\n",
" // 记录响应信息\n",
" Console.WriteLine($\"日志管道: LoggingHandler -> Send -> After Response: {response.StatusCode}\");\n",
" return response;\n",
" }\n",
"}\n",
"\n",
"/// <summary>\n",
"/// 令牌验证管道(中间件)\n",
"/// </summary>\n",
"public class TokenDelegatingHandler : DelegatingHandler \n",
"{\n",
" public TokenDelegatingHandler()\n",
" {\n",
" //防止成为最后一个管道时异常\n",
" this.InnerHandler = new SocketsHttpHandler();\n",
" }\n",
"\n",
" protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)\n",
" {\n",
" Console.WriteLine(\"令牌验证管道: TokenDelegatingHandler -> Send -> Added Token\");\n",
"\n",
" if (!request.Headers.Contains(Microsoft.Net.Http.Headers.HeaderNames.Authorization)) \n",
" {\n",
" Console.WriteLine(\"没有 Token, TokenDelegatingHandler 添加之\");\n",
" request.Headers.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, \"Bearer \" + \"a.b.c\");\n",
" }\n",
" else\n",
" {\n",
" Console.WriteLine($\"已有Token, {request.Headers.Authorization}\");\n",
" }\n",
"\n",
" HttpResponseMessage response = base.Send(request, cancellationToken);\n",
"\n",
" Console.WriteLine(\"令牌验证管道: TokenDelegatingHandler -> Send -> After\");\n",
"\n",
" return response;\n",
" }\n",
"\n",
" protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\n",
" {\n",
" Console.WriteLine(\"令牌验证管道: TokenDelegatingHandler -> SendAsync -> Before\");\n",
"\n",
" HttpResponseMessage response = await base.SendAsync(request, cancellationToken);\n",
"\n",
" Console.WriteLine(\"令牌验证管道: TokenDelegatingHandler -> SendAsync -> After\");\n",
"\n",
" return response;\n",
" }\n",
"}\n",
"\n",
"///<summary>\n",
"/// 异常处理管道(中间件)\n",
"/// </summary>\n",
"public class ExceptionDelegatingHandler : DelegatingHandler\n",
"{\n",
" public ExceptionDelegatingHandler()\n",
" {\n",
" //防止成为最后一个管道时异常\n",
" this.InnerHandler = new SocketsHttpHandler();\n",
" }\n",
"\n",
" protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)\n",
" {\n",
" Console.WriteLine(\"异常处理管道: ExceptionDelegatingHandler -> Send -> Before\");\n",
"\n",
" HttpResponseMessage response;\n",
" try \n",
" {\n",
" response = base.Send(request, cancellationToken);\n",
" response.EnsureSuccessStatusCode();\n",
" }\n",
" catch(Exception ex)\n",
" {\n",
" Console.WriteLine($\"异常管道中间件,监控到异常:{ex.Message}\");\n",
"\n",
" response = new HttpResponseMessage(HttpStatusCode.ExpectationFailed);\n",
" }\n",
" finally\n",
" {\n",
" //清理业务\n",
" }\n",
" \n",
" Console.WriteLine(\"异常处理管道: ExceptionDelegatingHandler -> Send -> After\");\n",
"\n",
" return response;\n",
" }\n",
"\n",
" protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\n",
" {\n",
" Console.WriteLine(\"异常处理管道: ExceptionDelegatingHandler -> SendAsync -> Before\");\n",
"\n",
" HttpResponseMessage response;\n",
" try \n",
" {\n",
" response = response = await base.SendAsync(request, cancellationToken);\n",
" response.EnsureSuccessStatusCode();\n",
" }\n",
" catch(Exception ex)\n",
" {\n",
" Console.WriteLine($\"异常管道中间件,监控到异常:{ex.Message}\");\n",
"\n",
" response = new HttpResponseMessage(HttpStatusCode.ExpectationFailed);\n",
" }\n",
" finally\n",
" {\n",
" //清理业务\n",
" }\n",
"\n",
" Console.WriteLine(\"异常处理管道: ExceptionDelegatingHandler -> SendAsync -> After\");\n",
"\n",
" return response;\n",
" }\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2、组合多个 HttpMessageHandler 形成请求/响应管道(处理链)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"组合管道,就是利用 DelegatingHandler 类的 InnerHandler 属性,指定本管道的下一个管道(中间件),将多个管道链接起来,形成一个`请求/响应`处理链。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"+ 默认管道"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"响应内容Pong\r\n"
]
}
],
"source": [
"{ //默认配置\n",
" var httpClient = new HttpClient()\n",
" {\n",
" BaseAddress = webApiBaseUri\n",
" };\n",
"\n",
" //发送请求,从日志看管道执行顺序\n",
" var response = await httpClient.GetAsync(\"/api/Hello/Ping\");\n",
"\n",
" var content = await response.Content.ReadAsStringAsync();\n",
"\n",
" Console.WriteLine($\"响应内容:{content}\");\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"+ 手动组合"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"令牌验证管道: TokenDelegatingHandler -> SendAsync -> Before\n",
"异常处理管道: ExceptionDelegatingHandler -> SendAsync -> Before\n",
"日志管道: LoggingHandler -> Send -> Before Request: GET http://127.0.0.1:5189/api/Config/GetApiConfig\n",
"日志管道: LoggingHandler -> Send -> After Response: OK\n",
"异常处理管道: ExceptionDelegatingHandler -> SendAsync -> After\n",
"令牌验证管道: TokenDelegatingHandler -> SendAsync -> After\n",
"响应内容:{\"data\":{\"host\":\"localhost\",\"port\":5189,\"scheme\":\"http\",\"pathBase\":\"\",\"baseUrl\":\"http://localhost:5189\",\"webAppMutexName\":\"HttpClientStudy.WebApp\"},\"code\":1,\"message\":\"成功\"}\n"
]
}
],
"source": [
"{ //组装管道\n",
"\n",
" /*\n",
" 1. 最后一个管道必须是SocketsHttpHandler 或者 保证最后的管道的InnerHandler有默认值\n",
" 2. 倒序组装正序执行HttpClient构造函数传入的HttpMessageHandler是第一个执行的其InnerHandler是下一个执行的依次类推\n",
" */\n",
"\n",
" var lastHandler = new SocketsHttpHandler()\n",
" {\n",
" UseCookies = false,\n",
" UseProxy = false,\n",
" };\n",
"\n",
" var logPipeline = new LoggingHandler()\n",
" {\n",
" InnerHandler = lastHandler\n",
" };\n",
" \n",
" var exceptionHandler = new ExceptionDelegatingHandler()\n",
" {\n",
" InnerHandler = logPipeline\n",
" };\n",
"\n",
" var tokenHandler = new TokenDelegatingHandler()\n",
" {\n",
" InnerHandler = exceptionHandler\n",
" };\n",
"\n",
" var httpClient = new HttpClient(tokenHandler)\n",
" {\n",
" BaseAddress = webApiBaseUri\n",
" };\n",
"\n",
" //发送请求,从日志看管道执行顺序\n",
" var response = await httpClient.GetAsync(\"/api/Config/GetApiConfig\");\n",
"\n",
" var content = await response.Content.ReadAsStringAsync();\n",
"\n",
" Console.WriteLine($\"响应内容:{content}\");\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"+ 动态组合"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"整个管道链的组装,是非常灵活的,动态组装也不是难事!"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [
{
"data": {
"text/plain": [
"http://127.0.0.1:5189"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"令牌验证管道: TokenDelegatingHandler -> SendAsync -> Before\n",
"日志管道: LoggingHandler -> Send -> Before Request: GET http://127.0.0.1:5189/api/Config/GetApiConfig\n",
"异常处理管道: ExceptionDelegatingHandler -> SendAsync -> Before\n",
"异常处理管道: ExceptionDelegatingHandler -> SendAsync -> After\n",
"日志管道: LoggingHandler -> Send -> After Response: OK\n",
"令牌验证管道: TokenDelegatingHandler -> SendAsync -> After\n",
"响应内容:{\"data\":{\"host\":\"localhost\",\"port\":5189,\"scheme\":\"http\",\"pathBase\":\"\",\"baseUrl\":\"http://localhost:5189\",\"webAppMutexName\":\"HttpClientStudy.WebApp\"},\"code\":1,\"message\":\"成功\"}\n"
]
}
],
"source": [
"/// <summary>\n",
"/// 管道构建器\n",
"/// </summary>\n",
"public class HttpMessageHandlerBuilder\n",
"{\n",
" public List<DelegatingHandler> Handlers { get; set; }\n",
"\n",
" public HttpMessageHandlerBuilder()\n",
" {\n",
" this.Handlers = new List<DelegatingHandler>();\n",
" }\n",
"\n",
" public HttpMessageHandlerBuilder(List<DelegatingHandler> handlers):base()\n",
" {\n",
" if(handlers != null)\n",
" {\n",
" this.Handlers = handlers;\n",
" }\n",
" }\n",
"\n",
" public void AddHttpMessageHandler(DelegatingHandler handler)\n",
" {\n",
" Handlers.Add(handler);\n",
" }\n",
"\n",
" public void RemoveHttpMessageHandler(DelegatingHandler handler)\n",
" {\n",
" Handlers.Remove(handler);\n",
" }\n",
"\n",
" public List<DelegatingHandler> FindHttpMessageHandler(DelegatingHandler handler)\n",
" {\n",
" return Handlers.Where(h => h.GetType() == handler.GetType()).ToList();\n",
" }\n",
"\n",
" /// <summary>\n",
" /// 根据管道配置创建一个新HttpClient实例\n",
" /// </summary>\n",
" public HttpClient Build()\n",
" {\n",
" if(this.Handlers ==null || this.Handlers.Count == 0)\n",
" {\n",
" return new HttpClient();\n",
" }\n",
"\n",
" //默认最终处理器\n",
" HttpMessageHandler defaultLastHandler = new SocketsHttpHandler()\n",
" {\n",
" //自定义配置\n",
" PooledConnectionLifetime = TimeSpan.FromSeconds(120),\n",
" };\n",
"\n",
" //倒序组装:完成后执行顺序与注册顺序一致\n",
" //拷贝份,省得多次调用有问题\n",
"\n",
" HttpMessageHandler currentHandler = new SocketsHttpHandler()\n",
" {\n",
" //自定义配置\n",
" PooledConnectionLifetime = TimeSpan.FromSeconds(120),\n",
" };\n",
"\n",
" var builderHandlers = this.Handlers.ToList<DelegatingHandler>();\n",
" builderHandlers.Reverse();\n",
"\n",
" if(builderHandlers[0].InnerHandler != null)\n",
" {\n",
" currentHandler = builderHandlers[0].InnerHandler;\n",
" }\n",
"\n",
" for (int i = 0; i < builderHandlers.Count; i++)\n",
" {\n",
" builderHandlers[i].InnerHandler = currentHandler;\n",
" currentHandler = builderHandlers[i];\n",
" }\n",
"\n",
" // 利用管道创建HttpClient\n",
" var httpClient = new HttpClient(currentHandler);\n",
"\n",
" return httpClient;\n",
" }\n",
"}\n",
"\n",
"//构建管道\n",
"List<DelegatingHandler> handlers = new List<DelegatingHandler>()\n",
"{\n",
" new TokenDelegatingHandler(),\n",
"};\n",
"\n",
"//添加\n",
"handlers.Add(new LoggingHandler());\n",
"handlers.Add(new ExceptionDelegatingHandler());\n",
"\n",
"var handlerBuilder = new HttpMessageHandlerBuilder(handlers);\n",
"\n",
"//第一次:发送请求,从日志看管道执行顺序\n",
"var httpClient = handlerBuilder.Build();\n",
"httpClient.BaseAddress = webApiBaseUri;\n",
"webApiBaseUrl.Display();\n",
"var response = await httpClient.GetAsync(\"/api/Config/GetApiConfig\");\n",
"var content = await response.Content.ReadAsStringAsync();\n",
"\n",
"Console.WriteLine($\"响应内容:{content}\");\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3、发送请求"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"和一般的请求,没什么区别。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4、管道执行顺序"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"+ 请求从最外层的 DelegatingHandler 开始,逐层进入内部处理器。\n",
"+ 响应则从最内层返回,经过每一层的 SendAsync 方法后返回给调用者。\n",
"\n",
"执行下面示例,查看日志输出:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"令牌验证管道: TokenDelegatingHandler -> SendAsync -> Before\n",
"异常处理管道: ExceptionDelegatingHandler -> SendAsync -> Before\n",
"日志管道: LoggingHandler -> Send -> Before Request: GET http://127.0.0.1:5189/api/Hello/Ping\n",
"日志管道: LoggingHandler -> Send -> After Response: OK\n",
"异常处理管道: ExceptionDelegatingHandler -> SendAsync -> After\n",
"令牌验证管道: TokenDelegatingHandler -> SendAsync -> After\n",
"响应内容Pong\n"
]
}
],
"source": [
"{ \n",
" //组装管道\n",
" //最后一个管道必须是SocketsHttpHandler或者默认的是SocketsHttpHandler\n",
" var lastHandler = new SocketsHttpHandler()\n",
" {\n",
" UseCookies = false,\n",
" UseProxy = false,\n",
" };\n",
"\n",
" var logPipeline = new LoggingHandler()\n",
" {\n",
" InnerHandler = lastHandler\n",
" };\n",
" \n",
" var exceptionHandler = new ExceptionDelegatingHandler()\n",
" {\n",
" InnerHandler = logPipeline\n",
" };\n",
"\n",
" var tokenHandler = new TokenDelegatingHandler()\n",
" {\n",
" InnerHandler = exceptionHandler\n",
" };\n",
"\n",
" var httpClient = new HttpClient(tokenHandler)\n",
" {\n",
" BaseAddress = webApiBaseUri\n",
" };\n",
"\n",
" //发送请求,从日志看管道执行顺序\n",
" var response = await httpClient.GetAsync(\"/api/Hello/Ping\");\n",
"\n",
" var content = await response.Content.ReadAsStringAsync();\n",
"\n",
" Console.WriteLine($\"响应内容:{content}\");\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5、使用 IHttpClientFactory 构建更复杂的管道(推荐)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 小结"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"| 特性 | 描述 |\n",
"|---------------------|----------------------------------------------------------------------|\n",
"| HttpMessageHandler | 是 HttpClient 的核心处理单元:ml-citation{ref=\"1\" data=\"citationList\"} |\n",
"| DelegatingHandler | 可用于构建自定义中间件逻辑:ml-citation{ref=\"1,5\" data=\"citationList\"} |\n",
"| 管道顺序 | 请求按顺序进入,响应逆序返回:ml-citation{ref=\"5,8\" data=\"citationList\"} |\n",
"| IHttpClientFactory | 推荐用于生产环境,支持灵活配置:ml-citation{ref=\"1,7\" data=\"citationList\"} |\n",
"\n",
"通过添加更多 DelegatingHandler 实现缓存、重试、认证等!"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".NET (C#)",
"language": "C#",
"name": ".net-csharp"
},
"language_info": {
"name": "python"
},
"polyglot_notebook": {
"kernelInfo": {
"defaultKernelName": "csharp",
"items": [
{
"aliases": [],
"name": "csharp"
}
]
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}