{ "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": [ "
Installed Packages
" ] }, "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": [ "/// \n", "/// 日志管道(中间件)\n", "/// \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 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", "/// \n", "/// 令牌验证管道(中间件)\n", "/// \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 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", "///\n", "/// 异常处理管道(中间件)\n", "/// \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 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": [ "/// \n", "/// 管道构建器\n", "/// \n", "public class HttpMessageHandlerBuilder\n", "{\n", " public List Handlers { get; set; }\n", "\n", " public HttpMessageHandlerBuilder()\n", " {\n", " this.Handlers = new List();\n", " }\n", "\n", " public HttpMessageHandlerBuilder(List 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 FindHttpMessageHandler(DelegatingHandler handler)\n", " {\n", " return Handlers.Where(h => h.GetType() == handler.GetType()).ToList();\n", " }\n", "\n", " /// \n", " /// 根据管道配置:创建一个新HttpClient实例\n", " /// \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();\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 handlers = new List()\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 }