diff --git a/Docs/0.目录结构.ipynb b/Docs/0.目录结构.ipynb index d9894da..e71881a 100644 --- a/Docs/0.目录结构.ipynb +++ b/Docs/0.目录结构.ipynb @@ -65,9 +65,9 @@ "+ [发送请求](./1.3.1.基础使用.发送请求.ipynb)\n", "+ [使用请求数据](./1.3.2.基础使用.使用请求数据.ipynb)\n", "+ [处理响应](./1.3.3.基础使用.处理响应.ipynb)\n", - "+ [处理错误](./1.3.4.基础使用.处理错误.ipynb)\n", - "+ [使用代理](./1.3.5.基础使用.使用代理.ipynb)\n", - "+ [使用Json](./1.3.6.基础使用.使用Json.ipynb)\n", + "+ [使用Json](./1.3.4.基础使用.使用Json.ipynb)\n", + "+ [处理错误](./1.3.5.基础使用.处理错误.ipynb)\n", + "+ [使用代理](./1.3.6.基础使用.使用代理.ipynb)\n", "+ [使用Cookie](./1.3.7.基础使用.使用Cookie.ipynb)" ] }, diff --git a/Docs/1.0.项目管理.ipynb b/Docs/1.0.项目管理.ipynb index 5d954ab..536dcd6 100644 --- a/Docs/1.0.项目管理.ipynb +++ b/Docs/1.0.项目管理.ipynb @@ -252,7 +252,11 @@ "cell_type": "code", "execution_count": null, "id": "7e6f752c", - "metadata": {}, + "metadata": { + "polyglot_notebook": { + "kernelName": "csharp" + } + }, "outputs": [ { "name": "stdout", diff --git a/Docs/1.3.0.基础使用.管理客户端.ipynb b/Docs/1.3.0.基础使用.管理客户端.ipynb index 1ea8e7d..bd6c95a 100644 --- a/Docs/1.3.0.基础使用.管理客户端.ipynb +++ b/Docs/1.3.0.基础使用.管理客户端.ipynb @@ -1319,7 +1319,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" @@ -1403,7 +1403,7 @@ "\n", "// 方式1(不推荐):类型化客户端:直接注入IoC,并从中获取实例。优点是范围可以自己选择。\n", "{\n", - " Console.WriteLine(\"方式1 -------------------------------------------------------------------\");\n", + " Console.WriteLine(\"方式1 直接注入IoC ------------------------------------------------------\");\n", " var services = new ServiceCollection();\n", " services.AddSingleton(b => \n", " { \n", @@ -1424,12 +1424,12 @@ " var dataB = await serverB.PingAsync();\n", " Console.WriteLine(dataB);\n", "\n", - " Console.WriteLine(\"========================================================================\");\n", + " Console.WriteLine(\"==============================结束======================================\");\n", "}\n", "\n", "// 方式2:类型化客户端:AddHttpClient<>() 设置\n", "{\n", - " Console.WriteLine(\"方式2 -------------------------------------------------------------------\");\n", + " Console.WriteLine(\"方式2 AddHttpClient<>() 设置---------------------------------------------\");\n", " var services = new ServiceCollection();\n", " services\n", " .AddHttpClient()\n", @@ -1455,12 +1455,12 @@ " var dataB = await serverB.PingAsync();\n", " Console.WriteLine(dataB);\n", "\n", - " Console.WriteLine(\"========================================================================\");\n", + " Console.WriteLine(\"==============================结束======================================\");\n", "}\n", "\n", "// 方式3:类型化客户端:结合工厂,由工厂从统一配置中提供类型化客户端中使用的HttpClient实例。\n", "{\n", - " Console.WriteLine(\"方式3 -------------------------------------------------------------------\");\n", + " Console.WriteLine(\"方式3 结合工厂-----------------------------------------------------------\");\n", " var services = new ServiceCollection();\n", " services.AddHttpClient(client => \n", " {\n", @@ -1502,7 +1502,7 @@ " var dataB2 = await serviceB2.PingAsync();\n", " Console.WriteLine(dataB2);\n", "\n", - " Console.WriteLine(\"========================================================================\");\n", + " Console.WriteLine(\"==============================结束======================================\");\n", "}" ] }, diff --git a/Docs/1.3.5.基础使用.使用代理.ipynb b/Docs/1.3.4.基础使用.使用Json.ipynb similarity index 100% rename from Docs/1.3.5.基础使用.使用代理.ipynb rename to Docs/1.3.4.基础使用.使用Json.ipynb diff --git a/Docs/1.3.4.基础使用.处理错误.ipynb b/Docs/1.3.4.基础使用.处理错误.ipynb deleted file mode 100644 index e77ccbe..0000000 --- a/Docs/1.3.4.基础使用.处理错误.ipynb +++ /dev/null @@ -1,205 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "dotnet_interactive": { - "language": "csharp" - }, - "polyglot_notebook": { - "kernelName": "csharp" - } - }, - "source": [ - "# HttpClient 处理错误与异常" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "根据客户端的创建方法不周,有不同的错误与异常处理方式。推荐类型化客户端、工厂和Polly库中统一处理!" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "dotnet_interactive": { - "language": "csharp" - }, - "polyglot_notebook": { - "kernelName": "csharp" - }, - "vscode": { - "languageId": "polyglot-notebook" - } - }, - "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
  • Microsoft.Extensions.DependencyInjection, 9.0.4
  • Microsoft.Extensions.Http, 9.0.4
  • Microsoft.Extensions.Http.Polly, 9.0.4
  • Microsoft.Extensions.Logging, 9.0.4
  • Microsoft.Extensions.Logging.Console, 9.0.4
  • Microsoft.Net.Http.Headers, 9.0.4
  • Polly, 8.5.2
  • Refit, 8.0.0
  • Refit.HttpClientFactory, 8.0.0
  • System.Net.Http.Json, 9.0.4
" - ] - }, - "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": {}, - "source": [ - "## 常规方法 try catch" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "直接实例化客户端时,可以用`Try Catch`简单处理,这种是最不推荐的方式!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "polyglot_notebook": { - "kernelName": "csharp" - }, - "vscode": { - "languageId": "polyglot-notebook" - } - }, - "outputs": [], - "source": [ - "/*\n", - " 每次请求都实例化:并发量大、请求频繁进会耗尽套接字端口\n", - "*/\n", - "{ \n", - " var baseUrl = WebApiConfigManager.GetWebApiConfig().BaseUrl;\n", - "\n", - " using(var client = new HttpClient())\n", - " {\n", - " //发送请求\n", - " var response = await client.GetAsync(baseUrl);\n", - " response.EnsureSuccessStatusCode();\n", - " }\n", - "\n", - " //显示句柄\n", - " var displayValue = display($\"第 0 次请求,成功!\");\n", - "\n", - " for(int i=0;i<10;i++)\n", - " {\n", - " using(var client = new HttpClient())\n", - " {\n", - " var response = await client.GetAsync(baseUrl);\n", - " response.EnsureSuccessStatusCode();\n", - " displayValue.Update($\"第 {i+1} 次/ 共 10 次请求,成功!\");\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 静态或工具类中处理" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 类型化客户端中处理" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 在管道中,添加异常中间件" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 工厂模式中统一处理" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Polly库处理" - ] - } - ], - "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 -} diff --git a/Docs/1.3.5.基础使用.处理错误.ipynb b/Docs/1.3.5.基础使用.处理错误.ipynb new file mode 100644 index 0000000..c711e91 --- /dev/null +++ b/Docs/1.3.5.基础使用.处理错误.ipynb @@ -0,0 +1,1021 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + } + }, + "source": [ + "# HttpClient 处理错误与异常" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "根据客户端的创建方法不周,有不同的错误与异常处理方式。推荐类型化客户端、工厂和Polly库中统一处理!" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "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
  • Microsoft.Extensions.DependencyInjection, 9.0.4
  • Microsoft.Extensions.Http, 9.0.4
  • Microsoft.Extensions.Http.Polly, 9.0.4
  • Microsoft.Extensions.Logging, 9.0.4
  • Microsoft.Extensions.Logging.Console, 9.0.4
  • Microsoft.Net.Http.Headers, 9.0.4
  • Polly, 8.5.2
  • Refit, 8.0.0
  • Refit.HttpClientFactory, 8.0.0
  • System.Net.Http.Json, 9.0.4
" + ] + }, + "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": {}, + "source": [ + "## 常规方法" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "直接实例化客户端时,可以用`Try Catch`或者`EnsureSuccessStatusCode方法`简单处理, 这种是最不推荐的方式!\n", + "\n", + "推荐使用后面介绍的 Pipeline 管道方式" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "+ try catch" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "polyglot_notebook": { + "kernelName": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "远程请求失败,响应码 InternalServerError\r\n" + ] + } + ], + "source": [ + "/*\n", + " try catch 处理异常\n", + "*/\n", + "try\n", + "{\n", + " var requestUri = new Uri(webApiBaseUri, \"/api/ErrorDemo/Error500\");\n", + "\n", + " using(var client = new HttpClient())\n", + " {\n", + " //发送请求\n", + " var response = await client.GetAsync(requestUri);\n", + " if(response.IsSuccessStatusCode)\n", + " {\n", + " Console.WriteLine($\"远程请求成功,响应码 {response.StatusCode}\");\n", + " }\n", + " else\n", + " {\n", + " Console.WriteLine($\"远程请求失败,响应码 {response.StatusCode}\");\n", + " }\n", + " }\n", + "}\n", + "catch(Exception ex)\n", + "{\n", + " Console.WriteLine($\"远程调用异常:{ex.Message}\");\n", + "}\n", + "finally\n", + "{\n", + " //清理业务\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "+ Response的EnsureSuccessStatusCode方法" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "polyglot_notebook": { + "kernelName": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [ + { + "ename": "Error", + "evalue": "System.Net.Http.HttpRequestException: Response status code does not indicate success: 500 (Internal Server Error).\r\n at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()\r\n at Submission#3.<>d__0.MoveNext()\r\n--- End of stack trace from previous location ---\r\n at Microsoft.CodeAnalysis.Scripting.ScriptExecutionState.RunSubmissionsAsync[TResult](ImmutableArray`1 precedingExecutors, Func`2 currentExecutor, StrongBox`1 exceptionHolderOpt, Func`2 catchExceptionOpt, CancellationToken cancellationToken)", + "output_type": "error", + "traceback": [ + "System.Net.Http.HttpRequestException: Response status code does not indicate success: 500 (Internal Server Error).\r\n", + " at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()\r\n", + " at Submission#3.<>d__0.MoveNext()\r\n", + "--- End of stack trace from previous location ---\r\n", + " at Microsoft.CodeAnalysis.Scripting.ScriptExecutionState.RunSubmissionsAsync[TResult](ImmutableArray`1 precedingExecutors, Func`2 currentExecutor, StrongBox`1 exceptionHolderOpt, Func`2 catchExceptionOpt, CancellationToken cancellationToken)" + ] + } + ], + "source": [ + "{ //EnsureSuccessStatusCode方法\n", + "\n", + " var requestUri = new Uri(webApiBaseUri, \"/api/ErrorDemo/Error500\");\n", + "\n", + " using(var client = new HttpClient())\n", + " {\n", + " //发送请求\n", + " var response = await client.GetAsync(requestUri);\n", + "\n", + " //确保响应码正确,否则异常\n", + " response.EnsureSuccessStatusCode();\n", + "\n", + " //输出响应码\n", + " Console.WriteLine($\"远程请求成功,响应码 {response.StatusCode}\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 静态或工具类中处理" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "静态类或工具类,可以在内部方法里使用 Try Catch 等常规方法进行异常处理。\n", + "\n", + "注意:推荐使用后面介绍的 Pipeline 管道方式" + ] + }, + { + "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": [ + "配置文件根目录:c:\\Users\\ruyu\\Desktop\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n", + "正常请求结果:{\"data\":{\"host\":\"localhost\",\"port\":5189,\"scheme\":\"http\",\"pathBase\":\"\",\"baseUrl\":\"http://localhost:5189\",\"webAppMutexName\":\"HttpClientStudy.WebApp\"},\"code\":1,\"message\":\"成功\"}\n", + "异常请求结果:StatusCode: 500, ReasonPhrase: 'Internal Server Error', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:\n", + "{\n", + " Date: Tue, 13 May 2025 04:30:54 GMT\n", + " Server: Kestrel\n", + " Transfer-Encoding: chunked\n", + " X-WebApi-UseTime: 685\n", + " Content-Type: text/plain; charset=utf-8\n", + "}\n" + ] + } + ], + "source": [ + "//静态类中处理异常\n", + "public class HttpClientHelper\n", + "{\n", + " public readonly static HttpClient StaticClient;\n", + "\n", + " static HttpClientHelper()\n", + " {\n", + " SocketsHttpHandler handler = new SocketsHttpHandler()\n", + " {\n", + " PooledConnectionLifetime = TimeSpan.FromSeconds(30),\n", + " };\n", + "\n", + " StaticClient = new HttpClient(handler)\n", + " {\n", + " //统一设置: 基础Uri\n", + " BaseAddress = new Uri(WebApiConfigManager.GetWebApiConfig().BaseUrl),\n", + " };\n", + "\n", + " //统一设置:请求头等\n", + " StaticClient.DefaultRequestHeaders.Add(\"x-custom-time\",\"12ms\");\n", + "\n", + " //统一错误处理:可以在Pipline中统一设置,后面有单独章节\n", + " } \n", + "\n", + " public static async Task GetAsync(string url)\n", + " {\n", + " //异常处理\n", + " try\n", + " {\n", + " return await StaticClient.GetAsync(url);\n", + " }\n", + " catch(Exception ex)\n", + " {\n", + " Console.WriteLine($\"远程调用发生异常:{ex.Message}\");\n", + " return await Task.FromResult(new HttpResponseMessage(HttpStatusCode.ExpectationFailed));\n", + " }\n", + " finally\n", + " {\n", + " //清理业务\n", + " }\n", + " }\n", + "\n", + " public static async Task GetStringAsync(string url)\n", + " {\n", + " var response = await StaticClient.GetAsync(url);\n", + "\n", + " //异常处理\n", + " response.EnsureSuccessStatusCode();\n", + " return await response.Content.ReadAsStringAsync();\n", + " }\n", + "\n", + " public static async Task PostAsync(string url, HttpContent content)\n", + " {\n", + " //异常处理\n", + " try\n", + " {\n", + " return await StaticClient.PostAsync(url, content);\n", + " }\n", + " catch(Exception ex)\n", + " {\n", + " Console.WriteLine($\"远程调用发生异常:{ex.Message}\");\n", + " return await Task.FromResult(new HttpResponseMessage(HttpStatusCode.ExpectationFailed));\n", + " }\n", + " finally\n", + " {\n", + " //清理业务\n", + " }\n", + " }\n", + "}\n", + "\n", + "{ //正常请求\n", + " var response = await HttpClientHelper.GetAsync(\"/api/Config/GetApiConfig\");\n", + " var content = await response.Content.ReadAsStringAsync();\n", + " Console.WriteLine($\"正常请求结果:{content}\");\n", + "\n", + " //异常请求\n", + " var response2 = await HttpClientHelper.GetAsync(\"/api/ErrorDemo/Error500\");\n", + " Console.WriteLine($\"异常请求结果:{response2}\");\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "+ 添加异常Pipeline管道(下面专项介绍)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 类型化客户端中处理" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "类型化客户端有三种方法:一是类内部处理;二是Pipeline,统一处理;三是结合IoC/工厂和Polly库,初始设置时统一处理。\n", + "\n", + "推荐第二、第三种,后面有专项介绍。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "+ 内部处理,类似静态或工具类" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "polyglot_notebook": { + "kernelName": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "HttpClientServiceA => 构造函数执行一次\n", + "System.Exception: 服务器异常\n", + " at HttpClientStudy.WebApp.Controllers.ErrorDemoController.Error500() in C:\\Users\\ruyu\\Desktop\\HttpClientStudy\\HttpClientStudy.WebApp\\Controllers\\ErrorDemoController.cs:line 37\n", + " at lambda_method3(Closure, Object, Object[])\n", + " at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)\n", + " at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Logged|12_1(ControllerActionInvoker invoker)\n", + " at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\n", + " at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)\n", + " at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\n", + " at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()\n", + "--- End of stack trace from previous location ---\n", + " at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\n", + " at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker)\n", + " at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker)\n", + " at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger)\n", + " at HttpClientStudy.WebApp.Program.<>c.<
b__0_12>d.MoveNext() in C:\\Users\\ruyu\\Desktop\\HttpClientStudy\\HttpClientStudy.WebApp\\Program.cs:line 258\n", + "--- End of stack trace from previous location ---\n", + " at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)\n", + " at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)\n", + " at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)\n", + " at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)\n", + " at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)\n", + " at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)\n", + "\n", + "HEADERS\n", + "=======\n", + "Host: 127.0.0.1:5189\n", + "traceparent: 00-8cccd683184035a261e8996c74c87f82-7440f8a982d79135-00\n", + "\n" + ] + } + ], + "source": [ + "// 类型化客户端A HttpClient\n", + "public class HttpClientServiceA\n", + "{\n", + " public HttpClient Client { get; }\n", + " public HttpClientServiceA(HttpClient client)\n", + " {\n", + " Client = client;\n", + " Console.WriteLine(\"HttpClientServiceA => 构造函数执行一次\");\n", + " }\n", + "\n", + " //常规方法:异常可在方法内处理\n", + " public async Task GetAsync()\n", + " {\n", + " var response = await Client.GetAsync(\"/api/ErrorDemo/Error500\");\n", + " var content = await response.Content.ReadAsStringAsync();\n", + " return content;\n", + " }\n", + "}\n", + "\n", + "// 使用类型化客户端\n", + "{\n", + " var services = new ServiceCollection();\n", + " services\n", + " .AddHttpClient()\n", + " .ConfigureHttpClient(client=>\n", + " {\n", + " client.BaseAddress = new Uri(webApiBaseUrl);\n", + " });\n", + "\n", + " var builder = services.BuildServiceProvider();\n", + " var serverA = builder.GetRequiredService();\n", + "\n", + " var dataA = await serverA.GetAsync();\n", + " Console.WriteLine(dataA);\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "+ 与工厂和Polly库统合,统一处理(下面专项介绍)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "+ 添加异常Pipeline管道(下面专项介绍)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 在管道中,添加异常中间件" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "不管那种方式创建的客户端,都可以配置Pipeline,专门添加异常管道中间件进行异常统一处理,也很推荐这种方式。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "+ 准备异常处理中间件" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "polyglot_notebook": { + "kernelName": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [], + "source": [ + "//异常中间件(管道)类\n", + "public class ExceptionDelegatingHandler : DelegatingHandler\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": [ + "+ 直接创建客户端时,加入异常管道" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "polyglot_notebook": { + "kernelName": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ExceptionDelegatingHandler -> SendAsync -> Before\n", + "异常管道中间件,监控到异常:Response status code does not indicate success: 500 (Internal Server Error).\n", + "ExceptionDelegatingHandler -> SendAsync -> After\n", + "远程请求失败,响应码 ExpectationFailed\n" + ] + } + ], + "source": [ + "{\n", + "\n", + " //构建管道(加入异常中间件)\n", + " var handler = new ExceptionDelegatingHandler()\n", + " {\n", + " //相当于下一个中间件(管道)\n", + " //最后中间件必须是SocketsHttpHandler\n", + " InnerHandler = new SocketsHttpHandler() \n", + " {\n", + " AllowAutoRedirect = true\n", + " }\n", + " };\n", + "\n", + " //构造中传入管道对象\n", + " using(var client = new HttpClient(handler){BaseAddress = new Uri(webApiBaseUrl)})\n", + " {\n", + " //发送请求\n", + " var response = await client.GetAsync(\"/api/ErrorDemo/Error500\");\n", + "\n", + " if(response.IsSuccessStatusCode)\n", + " {\n", + " Console.WriteLine($\"远程请求成功,响应码 {response.StatusCode}\");\n", + " }\n", + " else\n", + " {\n", + " Console.WriteLine($\"远程请求失败,响应码 {response.StatusCode}\");\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "+ 静态或工具类,加入异常管道" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "polyglot_notebook": { + "kernelName": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "配置文件根目录:c:\\Users\\ruyu\\Desktop\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n", + "ExceptionDelegatingHandler -> SendAsync -> Before\n", + "ExceptionDelegatingHandler -> SendAsync -> After\n", + "正常请求结果:{\"data\":{\"host\":\"localhost\",\"port\":5189,\"scheme\":\"http\",\"pathBase\":\"\",\"baseUrl\":\"http://localhost:5189\",\"webAppMutexName\":\"HttpClientStudy.WebApp\"},\"code\":1,\"message\":\"成功\"}\n", + "ExceptionDelegatingHandler -> SendAsync -> Before\n", + "异常管道中间件,监控到异常:Response status code does not indicate success: 500 (Internal Server Error).\n", + "ExceptionDelegatingHandler -> SendAsync -> After\n", + "异常请求结果:StatusCode: 417, ReasonPhrase: 'Expectation Failed', Version: 1.1, Content: System.Net.Http.EmptyContent, Headers:\n", + "{\n", + " Content-Length: 0\n", + "}\n" + ] + } + ], + "source": [ + "//静态类中处理异常\n", + "public class HttpClientHelper2\n", + "{\n", + " public readonly static HttpClient StaticClient;\n", + "\n", + " static HttpClientHelper2()\n", + " {\n", + " //构建管道(加入异常中间件)\n", + " var handler = new ExceptionDelegatingHandler()\n", + " {\n", + " //相当于下一个中间件(管道)\n", + " //最后中间件必须是SocketsHttpHandler\n", + " InnerHandler = new SocketsHttpHandler() \n", + " {\n", + " AllowAutoRedirect = true,\n", + " PooledConnectionLifetime = TimeSpan.FromSeconds(30),\n", + " }\n", + " };\n", + "\n", + " StaticClient = new HttpClient(handler)\n", + " {\n", + " //统一设置: 基础Uri\n", + " BaseAddress = new Uri(WebApiConfigManager.GetWebApiConfig().BaseUrl),\n", + " };\n", + "\n", + " //统一设置:请求头等\n", + " StaticClient.DefaultRequestHeaders.Add(\"x-custom-time\",\"12ms\");\n", + "\n", + " //统一错误处理:可以在Pipline中统一设置,后面有单独章节\n", + " } \n", + "\n", + " public static async Task GetAsync(string url)\n", + " {\n", + " return await StaticClient.GetAsync(url);\n", + " }\n", + "\n", + " public static async Task GetStringAsync(string url)\n", + " {\n", + " var response = await StaticClient.GetAsync(url);\n", + "\n", + " //无异常处理\n", + "\n", + " return await response.Content.ReadAsStringAsync();\n", + " }\n", + "\n", + " public static async Task PostAsync(string url, HttpContent content)\n", + " {\n", + " //无异常处理\n", + " return await StaticClient.PostAsync(url,content);\n", + " }\n", + "}\n", + "\n", + "{ //正常请求\n", + " var response = await HttpClientHelper2.GetAsync(\"/api/Config/GetApiConfig\");\n", + " var content = await response.Content.ReadAsStringAsync();\n", + " Console.WriteLine($\"正常请求结果:{content}\");\n", + "\n", + " //异常请求\n", + " var response2 = await HttpClientHelper2.GetAsync(\"/api/ErrorDemo/Error500\");\n", + " Console.WriteLine($\"异常请求结果:{response2}\");\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "+ 类型化客户端,加入异常管道" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "polyglot_notebook": { + "kernelName": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "HttpClientServiceC => 构造函数执行一次\n", + "ExceptionDelegatingHandler -> SendAsync -> Before\n", + "异常管道中间件,监控到异常:Response status code does not indicate success: 500 (Internal Server Error).\n", + "ExceptionDelegatingHandler -> SendAsync -> After\n", + "响应数据为:\n" + ] + } + ], + "source": [ + "// 类型化客户端A HttpClient\n", + "public class HttpClientServiceC\n", + "{\n", + " public HttpClient Client { get; }\n", + " public HttpClientServiceC(HttpClient client)\n", + " {\n", + " Client = client;\n", + " Console.WriteLine(\"HttpClientServiceC => 构造函数执行一次\");\n", + " }\n", + "\n", + " //常规方法:异常可在方法内处理\n", + " public async Task GetAsync()\n", + " {\n", + " var response = await Client.GetAsync(\"/api/ErrorDemo/Error500\");\n", + " var content = await response.Content.ReadAsStringAsync();\n", + " return content;\n", + " }\n", + "}\n", + "\n", + "// 使用类型化客户端\n", + "{\n", + " var services = new ServiceCollection();\n", + " services\n", + " //1、注入异常管道到服务\n", + " .AddTransient()\n", + " .AddHttpClient()\n", + " .ConfigureHttpClient(client=>\n", + " {\n", + " client.BaseAddress = new Uri(webApiBaseUrl);\n", + " })\n", + " //2、添加异常管道至客户端\n", + " .AddHttpMessageHandler();\n", + "\n", + " var builder = services.BuildServiceProvider();\n", + " var serverC = builder.GetRequiredService();\n", + "\n", + " var dataC = await serverC.GetAsync();\n", + " Console.WriteLine($\"响应数据为:{dataC}\");\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "+ 工厂,加入异常管道" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "polyglot_notebook": { + "kernelName": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ExceptionDelegatingHandler -> SendAsync -> Before\n", + "ExceptionDelegatingHandler -> SendAsync -> After\n", + "Pong\n", + "ExceptionDelegatingHandler -> SendAsync -> Before\n", + "异常管道中间件,监控到异常:Response status code does not indicate success: 500 (Internal Server Error).\n", + "ExceptionDelegatingHandler -> SendAsync -> After\n", + "响应码:ExpectationFailed\n" + ] + } + ], + "source": [ + "//使用\n", + "{\n", + " var services = new ServiceCollection();\n", + "\n", + " //基础配置\n", + " services\n", + " //1、注入异常管道到服务\n", + " .AddTransient()\n", + " //全局配置\n", + " .ConfigureHttpClientDefaults(clientBuilder =>\n", + " {\n", + " clientBuilder.AddDefaultLogger();\n", + " clientBuilder.ConfigureHttpClient(client => \n", + " {\n", + " client.BaseAddress = new Uri(webApiBaseUrl);\n", + " });\n", + " });\n", + "\n", + " //默认命名客户端\n", + " services.AddHttpClient(string.Empty, config => \n", + " {\n", + " config.DefaultRequestHeaders.Add(\"X-Custom-Demo\", \"true\");\n", + " })\n", + "\n", + " //配置客户端\n", + " .ConfigureHttpClient(client => \n", + " {\n", + " client.BaseAddress = new Uri(webApiBaseUrl);\n", + " client.Timeout = TimeSpan.FromSeconds(10);\n", + " })\n", + "\n", + " //添加类型化客户端\n", + " //.AddTypedClient()\n", + "\n", + " //2、添加异常管道至客户端\n", + " .AddHttpMessageHandler()\n", + "\n", + " //配置SocketsHttpHandler\n", + " .UseSocketsHttpHandler(config =>\n", + " {\n", + " //配置连接池等\n", + " config.Configure((handler,provider) => \n", + " {\n", + " handler.AllowAutoRedirect = true;\n", + " handler.PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30);\n", + " handler.PooledConnectionLifetime = TimeSpan.FromSeconds(30);\n", + " handler.UseProxy = false;\n", + " handler.UseCookies = true;\n", + " });\n", + " })\n", + " //设置生命周期\n", + " .SetHandlerLifetime(TimeSpan.FromSeconds(30))\n", + " //Polly策略配置\n", + " //.AddPolicyHandler(policy)\n", + " //便捷配置\n", + " //.AddTransientHttpErrorPolicy(builder => builder.CircuitBreakerAsync(11, TimeSpan.FromSeconds(30)))\n", + " ;\n", + "\n", + " \n", + " var factory = services.BuildServiceProvider().GetRequiredService();\n", + "\n", + " //正常请求\n", + " var defaultClient = factory.CreateClient();\n", + " var defaultContent = await defaultClient.GetStringAsync(\"api/hello/ping\");\n", + " Console.WriteLine(defaultContent);\n", + "\n", + " //异常请求\n", + " var clientA = factory.CreateClient();\n", + " var responseA = await clientA.GetAsync(\"/api/ErrorDemo/Error500\");\n", + " Console.WriteLine($\"响应码:{responseA.StatusCode}\");\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 工厂 + Polly库处理" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Polly库提供了多种多样的超时、异常等功能,非常推荐使用(可以配合IoC/工厂)。\n", + "\n", + "下面是简单例子:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "polyglot_notebook": { + "kernelName": "csharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "正常请求, 响应内容: Pong\n", + "运程调用发性异常,Polly进行了回退!\n", + "异常请求,响应码:InternalServerError\n" + ] + } + ], + "source": [ + "//polly策略\n", + "var policy = Policy\n", + " .Handle()\n", + " .OrResult(message => message.StatusCode != System.Net.HttpStatusCode.OK)\n", + " //后备策略,代替异常\n", + " .FallbackAsync(async fallbackAction =>\n", + " {\n", + " Console.WriteLine(\"运程调用发性异常,Polly进行了回退!\");\n", + "\n", + " return await Task.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError));\n", + " });\n", + "\n", + "//工厂中使用 Polly异常回退策略\n", + "{\n", + " var services = new ServiceCollection();\n", + "\n", + " //默认命名客户端\n", + " services\n", + " .AddHttpClient(string.Empty)\n", + " .ConfigureHttpClient(client => \n", + " {\n", + " client.BaseAddress = new Uri(webApiBaseUrl);\n", + " client.Timeout = TimeSpan.FromSeconds(10);\n", + " })\n", + "\n", + " //Polly策略配置\n", + " .AddPolicyHandler(policy);\n", + "\n", + " \n", + " var factory = services.BuildServiceProvider().GetRequiredService();\n", + "\n", + " //正常请求\n", + " var defaultClient = factory.CreateClient();\n", + " var defaultContent = await defaultClient.GetStringAsync(\"api/hello/ping\");\n", + " Console.WriteLine($\"正常请求, 响应内容: {defaultContent}\");\n", + "\n", + " //异常请求\n", + " var clientA = factory.CreateClient();\n", + " var responseA = await clientA.GetAsync(\"/api/ErrorDemo/Error500\");\n", + " Console.WriteLine($\"异常请求,响应码:{responseA.StatusCode}\");\n", + "}\n" + ] + } + ], + "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 +} diff --git a/Docs/1.3.6.基础使用.使用Json.ipynb b/Docs/1.3.6.基础使用.使用代理.ipynb similarity index 100% rename from Docs/1.3.6.基础使用.使用Json.ipynb rename to Docs/1.3.6.基础使用.使用代理.ipynb diff --git a/Docs/Assets/HttpClient-异常处理.jpg b/Docs/Assets/HttpClient-异常处理.jpg new file mode 100644 index 0000000..876587c Binary files /dev/null and b/Docs/Assets/HttpClient-异常处理.jpg differ diff --git a/Docs/Ini.ipynb b/Docs/Ini.ipynb index bce8308..e89ffe6 100644 --- a/Docs/Ini.ipynb +++ b/Docs/Ini.ipynb @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": { "polyglot_notebook": { "kernelName": "csharp" @@ -22,7 +22,7 @@ { "data": { "text/html": [ - "
Installed Packages
  • Microsoft.Extensions.DependencyInjection, 9.0.3
  • Microsoft.Extensions.Http, 9.0.3
  • Microsoft.Extensions.Http.Polly, 9.0.3
  • Microsoft.Extensions.Logging, 9.0.3
  • Microsoft.Extensions.Logging.Console, 9.0.3
  • Microsoft.Net.Http.Headers, 9.0.3
  • Polly, 8.5.2
  • Refit, 8.0.0
  • Refit.HttpClientFactory, 8.0.0
  • System.Net.Http.Json, 9.0.3
" + "
Installed Packages
  • Microsoft.Extensions.DependencyInjection, 9.0.4
  • Microsoft.Extensions.Http, 9.0.4
  • Microsoft.Extensions.Http.Polly, 9.0.4
  • Microsoft.Extensions.Logging, 9.0.4
  • Microsoft.Extensions.Logging.Console, 9.0.4
  • Microsoft.Net.Http.Headers, 9.0.4
  • Polly, 8.5.2
  • Refit, 8.0.0
  • Refit.HttpClientFactory, 8.0.0
  • System.Net.Http.Json, 9.0.4
" ] }, "metadata": {}, @@ -32,10 +32,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "配置文件根目录:d:\\软件项目\\学习项目\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n", - "配置文件根目录:d:\\软件项目\\学习项目\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n", + "配置文件根目录:c:\\Users\\ruyu\\Desktop\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n", + "配置文件根目录:c:\\Users\\ruyu\\Desktop\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n", "启动WebApi项目...\n", - "程序[d:\\软件项目\\学习项目\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.WebApp\\HttpClientStudy.WebApp.exe]已在新的命令行窗口执行。如果未出现新命令行窗口,可能是程序错误造成窗口闪现!\n", + "程序[c:\\Users\\ruyu\\Desktop\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.WebApp\\HttpClientStudy.WebApp.exe]已在新的命令行窗口执行。如果未出现新命令行窗口,可能是程序错误造成窗口闪现!\n", "已启动WebApi项目,保持窗口打开状态!\n", "初始化完成!\n" ] @@ -106,6 +106,7 @@ "\n", "//全局变量\n", "var webApiBaseUrl = WebApiConfigManager.GetWebApiConfig().BaseUrl;\n", + "var webApiBaseUri = new Uri(webApiBaseUrl);\n", "var workDir = Environment.CurrentDirectory;\n", "var fullPath = System.IO.Path.GetFullPath(\"./Publish/HttpClientStudy.WebApp/HttpClientStudy.WebApp.exe\", workDir);\n", "//fullPath.Display();\n",