|
|
|
@ -0,0 +1,710 @@
|
|
|
|
|
{
|
|
|
|
|
"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
|
|
|
|
|
}
|