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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

{
"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
}