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.3.5.基础使用.处理错误.ipynb

1022 lines
34 KiB
Plaintext

{
"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": [
"<div><div></div><div></div><div><strong>Installed Packages</strong><ul><li><span>Microsoft.Extensions.DependencyInjection, 9.0.4</span></li><li><span>Microsoft.Extensions.Http, 9.0.4</span></li><li><span>Microsoft.Extensions.Http.Polly, 9.0.4</span></li><li><span>Microsoft.Extensions.Logging, 9.0.4</span></li><li><span>Microsoft.Extensions.Logging.Console, 9.0.4</span></li><li><span>Microsoft.Net.Http.Headers, 9.0.4</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.4</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": {},
"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.<<Initialize>>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.<<Initialize>>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<HttpResponseMessage> 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<string> 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<HttpResponseMessage> 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.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)\n",
" at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>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.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\n",
" at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)\n",
" at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)\n",
" at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger)\n",
" at HttpClientStudy.WebApp.Program.<>c.<<Main>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<string> 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<HttpClientServiceA>()\n",
" .ConfigureHttpClient(client=>\n",
" {\n",
" client.BaseAddress = new Uri(webApiBaseUrl);\n",
" });\n",
"\n",
" var builder = services.BuildServiceProvider();\n",
" var serverA = builder.GetRequiredService<HttpClientServiceA>();\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<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": [
"+ 直接创建客户端时,加入异常管道"
]
},
{
"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<HttpResponseMessage> GetAsync(string url)\n",
" {\n",
" return await StaticClient.GetAsync(url);\n",
" }\n",
"\n",
" public static async Task<string> 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<HttpResponseMessage> 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<string> 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<ExceptionDelegatingHandler>()\n",
" .AddHttpClient<HttpClientServiceC>()\n",
" .ConfigureHttpClient(client=>\n",
" {\n",
" client.BaseAddress = new Uri(webApiBaseUrl);\n",
" })\n",
" //2、添加异常管道至客户端\n",
" .AddHttpMessageHandler<ExceptionDelegatingHandler>();\n",
"\n",
" var builder = services.BuildServiceProvider();\n",
" var serverC = builder.GetRequiredService<HttpClientServiceC>();\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<ExceptionDelegatingHandler>()\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<HttpClient>(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<HttpClientServiceC>()\n",
"\n",
" //2、添加异常管道至客户端\n",
" .AddHttpMessageHandler<ExceptionDelegatingHandler>()\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<HttpResponseMessage>(11, TimeSpan.FromSeconds(30)))\n",
" ;\n",
"\n",
" \n",
" var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();\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<HttpRequestException>()\n",
" .OrResult<HttpResponseMessage>(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<HttpClient>(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<IHttpClientFactory>();\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
}