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

This file contains ambiguous Unicode characters!

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

{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"source": [
"# HttpClient 处理错误与异常"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"根据客户端的创建方法不周有不同的错误与异常处理方式。推荐类型化客户端、工厂和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
}