{
"cells": [
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
"source": [
"# HttpClient 初始化与生命周期管理"
]
"metadata": {},
"HttpClient 旨在实例化一次,并在应用程序的整个生命周期内重复使用。\n",
"\n",
"为实现复用,HttpClient类库默认使用连接池和请求管道,可以手动管理(连接池、配置管道、使用Polly); 结合IoC容器、工厂模式(提供了IHttpClientFactory类库)、复原库Polly,可以更加方便、完善的使用,这也是推荐的方法。"
"## 0、初始化与全局设置"
"cell_type": "code",
"execution_count": null,
"vscode": {
"languageId": "polyglot-notebook"
"outputs": [],
"//全局设置,行运行一次,为后续准备\n",
"#r \"nuget:System.Net.Http.Json\"\n",
"#r \"nuget:Microsoft.Extensions.Http\"\n",
"#r \"nuget:Microsoft.Extensions.DependencyInjection\"\n",
"#r \"nuget:Polly\"\n",
"#r \"nuget:Microsoft.Extensions.Http.Polly\"\n",
"#r \"nuget:Refit\" \n",
"#r \"nuget:Refit.HttpClientFactory\"\n",
"#r \"./Publish/HttpClientStudy.Core/HttpClientStudy.Core.dll\"\n",
"global using System;\n",
"global using System.IO;\n",
"global using System.IO.Enumeration;\n",
"global using System.Buffers;\n",
"global using System.Collections;\n",
"global using System.Collections.Concurrent;\n",
"global using System.Linq;\n",
"global using System.Linq.Expressions;\n",
"global using System.Threading;\n",
"global using System.Threading.Tasks;\n",
"global using System.Net.Http;\n",
"global using System.Net.Http.Headers;\n",
"global using Microsoft.Extensions.DependencyInjection;\n",
"global using Microsoft.Extensions.DependencyInjection.Extensions;\n",
"global using Polly;\n",
"global using Polly.NoOp;\n",
"global using Polly.Simmy;\n",
"global using Polly.Retry;\n",
"global using Polly.Hedging;\n",
"global using Polly.Timeout;\n",
"global using Polly.Bulkhead;\n",
"global using Polly.Fallback;\n",
"global using Polly.RateLimit;\n",
"global using Polly.CircuitBreaker;\n",
"global using Polly.Utilities;\n",
"global using Polly.Extensions;\n",
"global using Polly.Wrap;\n",
"global using Polly.Registry;\n",
"global using Polly.Telemetry;\n",
"global using Refit;\n",
"//global using Refit.HttpClientFactory;\n",
"global using HttpClientStudy.Config;\n",
"global using HttpClientStudy.Core;\n",
"global using HttpClientStudy.Core.Utilities;\n",
"//全局变量\n",
"var webApiBaseUrl = WebApiConfigManager.GetWebApiConfig().BaseUrl;\n",
"var workDir = Environment.CurrentDirectory;\n",
"var fullPath = System.IO.Path.GetFullPath(\"./Publish/HttpClientStudy.WebApp/HttpClientStudy.WebApp.exe\", workDir);\n",
"//fullPath.Display();\n",
"//启动已发布的WebApi项目\n",
"Console.WriteLine(\"启动WebApi项目\");\n",
"var startMessage = AppUtility.RunWebApiExeFile(fullPath);\n",
"Console.WriteLine(startMessage);\n"
"## 1、手动管理:直接实例化-强烈不推荐"
"下面这种每次使用都实例化的用法是最常见、最`不推荐的`:\n",
"因为HttpClient刚推出时不成熟及微软官方文档的示例代码是这种用法,再加上这种是最简单方便的使用方法,就造成很多人使用这种用法。\n",
"这种方法有如下缺点:\n",
" 1. 每次使用都实例化,造成性能开销大、容易内存泄露;\n",
" 2. 并发量大、请求频繁时:网络端口会被耗尽 `Using包HttpClient,也只是在应用进程中释放了HttpClient实例,但http请求/响应是跨操作系统和网络的,而系统及网络问题在进程之上,不是进程所能处理的。`\n",
" \n",
"优点:\n",
" 1. 使用简单,好学易用;\n",
" 2. 并发量小且请求不频繁时,问题不大;"
"/*\n",
" 每次请求都实例化:并发量大、请求频繁进会耗尽套接字端口\n",
"*/\n",
"{ \n",
" var baseUrl = WebApiConfigManager.GetWebApiConfig().BaseUrl;\n",
" using(var client = new HttpClient())\n",
" {\n",
" //发送请求\n",
" var response = await client.GetAsync(baseUrl);\n",
" response.EnsureSuccessStatusCode();\n",
" }\n",
" //显示句柄\n",
" var displayValue = display($\"第 1 次请求,成功!\");\n",
" for(int i=0;i<10;i++)\n",
" displayValue.Update($\"第 {i+1} 次/ 共 10 次请求,成功!\");\n",
"}"
"## 2、手动管理:静态类或单例"
"相比于直接new,实现了HttpClient的重用,`不推荐的`:\n",
"缺点:\n",
" 1. 不够灵活、优雅:特别是有多个系列的请求时;\n",
" 1. 复用 HttpClient\n",
" 2. 实现了HttpClient的重用,减少创建和销毁的开销"
" 静态类/属性\n",
"public class HttpClientHelper\n",
"{\n",
" public readonly static HttpClient StaticClient;\n",
" static HttpClientHelper()\n",
" SocketsHttpHandler handler = new SocketsHttpHandler()\n",
" PooledConnectionLifetime = TimeSpan.FromSeconds(30),\n",
" };\n",
" StaticClient = new HttpClient(handler);\n",
" //统一设置:请求头等\n",
" //统一错误处理\n",
" //当然这里也可以设置Pipline,不过这里就不演示了\n",
" } \n",
" public static async Task<HttpResponseMessage> GetAsync(string url)\n",
" return await StaticClient.GetAsync(url);\n",
" public static async Task<string> GetStringAsync(string url)\n",
" var response = await StaticClient.GetAsync(url);\n",
" return await response.Content.ReadAsStringAsync();\n",
" public static async Task<HttpResponseMessage> PostAsync(string url, HttpContent content)\n",
" return await StaticClient.PostAsync(url, content);\n",
"}\n",
"{ //调用静态类\n",
" var response = await HttpClientHelper.GetAsync(baseUrl+\"/api/Config/GetApiConfig\");\n",
" var content = await response.Content.ReadAsStringAsync();\n",
" Console.WriteLine(content);\n",
" var response2 = await HttpClientHelper.GetStringAsync(baseUrl+\"/api/Normal/GetAllAccounts\");\n",
" Console.WriteLine(response2);\n",
" 单例实现1:\n",
" 1. 私有构造函数,防止外部实例化\n",
" 2. 使用静态只读变量存储类的实例,由.Net框架保证实例不变且线程安全\n",
" 3. 密封类,拒绝继承,保证不被子类破坏\n",
"// 使用Lazy<T>实现单例\n",
"public sealed class HttpClientSingleton \n",
" // 私有静态变量,用于存储类的实例\n",
" private static readonly HttpClientSingleton instance = new HttpClientSingleton();\n",
" //公共静态属性,用于获取类的实例\n",
" public static HttpClientSingleton Instance\n",
" get\n",
" return instance;\n",
" private readonly HttpClient Client;\n",
" //私有构造函数,防止外部实例化\n",
" private HttpClientSingleton() \n",
" Client = new HttpClient(handler);\n",
" //可以使用IoC容器来管理\n",
" Console.WriteLine(\"HttpClientSingleton 初始化一次\");\n",
" public async Task<HttpResponseMessage> GetAsync(string url)\n",
" return await Client.GetAsync(url);\n",
" public async Task<string> GetStringAsync(string url)\n",
" var response = await Client.GetAsync(url);\n",
"{ //调用示例\n",
" var response = await HttpClientSingleton.Instance.GetAsync(baseUrl+\"/api/Config/GetApiConfig\");\n",
" var response2 = await HttpClientSingleton.Instance.GetStringAsync(baseUrl+\"/api/Normal/GetAllAccounts\");\n",
" 单例实现2:\n",
" 2. 使用Lazy<T>, 延迟实例化, 由.Net 框架保证线程安全\n",
"// 由于静态初始化器是由 .NET 运行时在后台处理的,因此它是线程安全的,不需要额外的锁定机制。\n",
"public sealed class HttpClientSingleton2\n",
" private static readonly Lazy<HttpClient> _httpClientLazy = new Lazy<HttpClient>(() =>\n",
" PooledConnectionLifetime = TimeSpan.FromSeconds(30)\n",
" var client = new HttpClient(handler)\n",
" // 可以在这里配置HttpClient的实例,例如设置超时时间、基地址等\n",
" //Timeout = TimeSpan.FromSeconds(30),\n",
" //BaseAddress = new Uri(\"https://api.example.com/\"),\n",
" Console.WriteLine(\"HttpClientSingleton2 初始化一次\");\n",
" return client;\n",
" });\n",
" public static HttpClient Instance => _httpClientLazy.Value;\n",
" // 私有构造函数,防止外部实例化\n",
" private HttpClientSingleton2() { } \n",
" return await _httpClientLazy.Value.GetAsync(url);\n",
" var response = await _httpClientLazy.Value.GetAsync(url);\n",
" var response = await HttpClientSingleton2.Instance.GetAsync(baseUrl+\"/api/Config/GetApiConfig\");\n",
" var response2 = await HttpClientSingleton2.Instance.GetStringAsync(baseUrl+\"/api/Normal/GetAllAccounts\");\n",
"## 3、手动管理:多工具类(每类请求对应一种工具类或单例类)"
"把不同类别的请求分成不同的工具类,业务类直接封装成工具类的方法。类似类型化的客户端。 简单使用的话,`比较推荐`\n",
"优点:\n",
" 1. 复用HttpClient\n",
" 2. 可以灵活的进行统一配置\n",
" 3. 不同类别不同工具类,方便定制\n",
" 4. 业务直接封装成工具类方法,调用方便、快捷\n",
" 1. 工具类比较多,需要手动维护\n",
" 2. 工具类方法比较多且和业务直接相关,需要手动维护\n"
"// 百度服务类\n",
"public sealed class BaiduService \n",
" private readonly HttpClient _httpClient;\n",
" public BaiduService()\n",
" //初始化httpClient\n",
" var baseHander = new SocketsHttpHandler() \n",
" { \n",
" MaxConnectionsPerServer = 1000 \n",
" _httpClient = new HttpClient(baseHander)\n",
" Timeout = TimeSpan.FromSeconds(10),\n",
" BaseAddress = new Uri(\"http://www.baidu.com\"),\n",
" ///// <summary>\n",
" /// 获取百度首页长度\n",
" /// </summary>\n",
" public async Task<int> GetIndexLengthAsync(string url)\n",
" var response = await _httpClient.GetAsync(url);\n",
" var result = await response.Content.ReadAsStringAsync();\n",
" return result.Length;\n",
"//调用示例\n",
" var service = new BaiduService();\n",
" var result = await service.GetIndexLengthAsync(\"/\");\n",
" Console.WriteLine(result);\n",
"// 本机服务类\n",
"public sealed class LocalService \n",
" public LocalService()\n",
" BaseAddress = new Uri(WebApiConfigManager.GetWebApiConfig().BaseUrl),\n",
" public async Task<string> GetIndexAsync(string url)\n",
" return result;\n",
" var service2 = new LocalService();\n",
" var result = await service2.GetIndexAsync(\"/api/Simple/GetAccount\");\n",
"## 4、手动管理:可复原(Polly)请求"
"using Polly;\n",
"using Polly.Simmy;\n",
"using Polly.Retry;\n",
"using Polly.Extensions;\n",
" var pipleLine = new ResiliencePipelineBuilder()\n",
" .AddRetry(new RetryStrategyOptions()\n",
" ShouldHandle = new PredicateBuilder().Handle<Exception>(),\n",
" MaxRetryAttempts = 3, // Retry up to 3 times\n",
" OnRetry = args =>\n",
" // Due to how we have defined ShouldHandle, this delegate is called only if an exception occurred.\n",
" // Note the ! sign (null-forgiving operator) at the end of the command.\n",
" var exception = args.Outcome.Exception!; // The Exception property is nullable\n",
" Console.WriteLine(\"内部重试\");\n",
" return default;\n",
" })\n",
" .Build();\n",
" var BaseUrl = WebApiConfigManager.GetWebApiConfig().BaseUrl;\n",
" HttpClient client = new HttpClient(new SocketsHttpHandler(){})\n",
" BaseAddress = new Uri(BaseUrl),\n",
" try\n",
" await pipleLine.ExecuteAsync(async (inneerToken)=>\n",
" var response = await client.GetAsync(\"api/Polly8/Retry_Exception\",inneerToken);\n",
" catch(Exception ex)\n",
" Console.WriteLine(ex.Message);\n",
" finally\n",
"## 5、IoC容器管理"
"### 直接注册IoC"
" 注意:\n",
" 1、直接IoC管理:只能一个,不太方便;\n",
" 2、可使用.NET 8+ 的 KeyedService, 可以管理多个。 老版只能用服务集合,勉强能用;\n",
" 3、把HttpClient 放在多个类中,分别注册使用;不过这样,不如直接使用类型化客户端;\n",
"{ // 直接使用\n",
" var services = new ServiceCollection();\n",
" services.AddSingleton<HttpClient>(new HttpClient()\n",
" //BaseAddress = new Uri(\"https://localhost:5001/\"),\n",
" var client = services.BuildServiceProvider().GetRequiredService<HttpClient>();\n",
" var resp = await client.GetAsync(\"https://www.baidu.com\");\n",
" resp.EnsureSuccessStatusCode();\n",
" var content = await resp.Content.ReadAsStringAsync();\n",
" Console.WriteLine(content.Length);\n",
"{ // KeyService: .Net 8+ 才支持的功能\n",
" services\n",
" .AddKeyedSingleton<HttpClient>(\"HttpClientA\",new HttpClient()\n",
" BaseAddress = new Uri(\"https://www.baidu.com/\"),\n",
" .AddKeyedSingleton<HttpClient>(\"HttpClientB\", new HttpClient()\n",
" BaseAddress = new Uri(\"https://www.qq.com/\"),\n",
" Timeout = TimeSpan.FromSeconds(2),\n",
" var clientA = services.BuildServiceProvider().GetRequiredKeyedService<HttpClient>(\"HttpClientA\");\n",
" var responseA = await clientA.GetAsync(\"/\");\n",
" responseA.EnsureSuccessStatusCode();\n",
" var contentA = await responseA.Content.ReadAsStringAsync();\n",
" Console.WriteLine(contentA.Length);\n",
" var clientB = services.BuildServiceProvider().GetRequiredKeyedService<HttpClient>(\"HttpClientB\");\n",
" var responseB = await clientB.GetAsync(\"/\");\n",
" responseB.EnsureSuccessStatusCode();\n",
" var contentB= await responseB.Content.ReadAsStringAsync();\n",
" Console.WriteLine(contentB.Length);\n",
"### HttpClient 多服务类"
"// IoC 多个HttpClient服务类\n",
"public class HttpClientServerA\n",
" public static HttpClient Client = new HttpClient()\n",
" public int GetBaiduIndexLength()\n",
" var requestMessage = new HttpRequestMessage(HttpMethod.Get, \"/\");\n",
" var response = Client.Send(requestMessage);\n",
" var s = response.Content.ReadAsStream();\n",
" return (int)s.Length;\n",
"public class HttpClientServerB\n",
" services.AddScoped<HttpClientServerA>();\n",
" services.AddScoped<HttpClientServerB>();\n",
" var provider = services.BuildServiceProvider();\n",
" var clientA = provider.GetService<HttpClientServerA>();\n",
" var sumA = clientA.GetBaiduIndexLength();\n",
" Console.WriteLine($\"A: {sumA}\");\n",
" var clientB = provider.GetService<HttpClientServerB>();\n",
" var sumB = clientB.GetBaiduIndexLength();\n",
" Console.WriteLine($\"A: {sumB}\");\n",
"## 6、客户端工厂管理:IHttpClientFactory(需要结合IoC) `强力推荐`"
"使用 IHttpClientFactory 创建和管理 `短期HttpClient` 是官方强力推荐的方式。特别是使用IoC或是 ASP.NET中后台调用其它接口的情况。\n",
"IHttpClientFactory 综合使用了 HttpClient的多种特性:HttpClient的生命周期、HttpClient的配置、HttpClient的拦截器、HttpClient的缓存、HttpClient的依赖注入、Polly等等。\n"
"### 默认客户端"
"从使用推测,设计 IHttpClientFactory 时,重点应该是使用 “命名客户端” 或 “类型化客户端” 而不是默认客户端。 \n",
"只有 AddHttpClient() 扩展方法返回 IServiceCollection;其它相关扩展方法( AddHttpClient<HttpClient>())均返回 IHttpClientBuilder,明显针对命名客户端。\n",
"AddHttpClient() 相当于注册了基本框架;而命名客户端中,名称为空(\"\"或string.Empty)的,相当于默认客户端。\n",
"有一个 名为 `ConfigureHttpClientDefaults` 的 ServiceCollection 对象的扩展方法,用于配置所有HttpClient实例,并且只在初始化时执行一次。如果只使用一个默认客户端的话,可以使用 ConfigureHttpClientDefaults 和 AddHttpClient() 配合使用,也能达到默认客户端的配置效果。 "
"//方式1:默认客户端\n",
" /*\n",
" AddHttpClient() 返回 ServiceCollection,可以继续添加其他客户端。\n",
" 其它方法则返回IHttpClientBuilder,后结配置的扩展方法,只能针对当前前端那个命名命令端。\n",
" */\n",
" services.AddHttpClient();\n",
" var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();\n",
" var client = factory.CreateClient();\n",
" //或者\n",
" var client2 = factory.CreateClient(\"\");\n",
" //或者 内部都是使用CreateClient(string.Empty),表示默认客户端。\n",
" var client3 = factory.CreateClient(string.Empty);\n",
" var response = await client.GetAsync(webApiBaseUrl + \"/api/hello/index\");\n",
" var data = await response.Content.ReadAsStringAsync();\n",
" data.Display();\n",
"//方式2:默认客户端 + 默认配置\n",
" //默认客户端\n",
" //配置所有客户端\n",
" services.ConfigureHttpClientDefaults(builder => \n",
" //配置构建器\n",
" //builder.AddDefaultLogger();\n",
" //配置客户端\n",
" builder.ConfigureHttpClient(c=>\n",
" c.BaseAddress = new Uri(webApiBaseUrl);\n",
" var response = await client.GetAsync(\"/api/hello/ping\");\n",
"//方式3(推荐):默认客户端:直接使用名称为 string.empty 的命名客户端\n",
" .AddHttpClient<HttpClient>(string.Empty)\n",
" //这样后续的配置,都是针对 string.empty 的客户端,可以使用全部配置功能\n",
" .ConfigureHttpClient(c=>c.BaseAddress = new Uri(webApiBaseUrl))\n",
" .AddDefaultLogger();\n",
"//错误用法\n",
" //没有参数时,导致后面配置不起使用;\n",
" //参数必须为 空字符串或string.Empty,后续的配置才能起使用\n",
" .AddHttpClient<HttpClient>()\n",
" //没有参数时,导致后面配置不起使用\n",
" catch(InvalidOperationException ex)\n",
" Console.WriteLine($\"没有参数的配置:AddHttpClient<HttpClient>(),因后续配置中,赋值 BaseAddress 不起使用,出现异常:{Environment.NewLine}{ex.Message}\");\n",
" client.Dispose();\n",
"### 默认全局配置"
"ConfigureHttpClientDefaults 扩展方法,添加一个委托,用于配置所有HttpClient实例。只执行一次。"
"//全局配置:所有HttpClient配置\n",
" //添加一个委托,用于配置所有HttpClient实例。\n",
" //只执行一次,而非每次CreateClient,都会执行一次。\n",
" //builder.UseSocketsHttpHandler();\n",
" //builder.SetHandlerLifetime(TimeSpan.FromMinutes(5));\n",
" builder.ConfigureHttpClient(hc =>\n",
" hc.BaseAddress = new Uri(webApiBaseUrl);\n",
" Console.WriteLine(\"ConfigureHttpClientDefaults 只执行一次!\");\n",
" //配置命名客户端\n",
" .AddHttpClient<HttpClient>(\"client_a\")\n",
" .ConfigureHttpClient(hc => \n",
" hc.DefaultRequestHeaders.Add(\"client_a\", \"client_a\");\n",
" //可以覆盖默认配置\n",
" //hc.BaseAddress = new Uri(\"http://www.qq.com\");\n",
" Console.WriteLine(\"ConfigureHttpClient 每次 CreateClient 执行一次!\");\n",
" var defaultClient = factory.CreateClient();\n",
" var defaultResponse = await defaultClient.GetAsync(\"/api/hello/ping\");\n",
" var defaultData = await defaultResponse.Content.ReadAsStringAsync();\n",
" Console.WriteLine(defaultData);\n",
" //命名客户端\n",
" var namedClient = factory.CreateClient(\"client_a\");\n",
" var namedResponse = await namedClient.GetAsync(\"/api/hello/get\");\n",
" var namedData = await namedResponse.Content.ReadAsStringAsync();\n",
" Console.WriteLine(namedData);\n",
" _ = factory.CreateClient(\"client_a\");\n",
"### 命名客户端(推荐用法)"
"命名客户端,应该是官方推荐的方法。名称为空字符串或string.Empty时,可以为是默认命名客户端,factory.CreateClient()创建的就是这个默认客户端(或者factory.CreateClient(\"\"))。"
"//命名客户端\n",
" var clientA =\"httpClientA\";\n",
" var clientB =\"httpClientB\";\n",
" services.AddHttpClient<HttpClient>(string.Empty, (provider, client) => \n",
" client.BaseAddress = new Uri(webApiBaseUrl);\n",
" services.AddHttpClient<HttpClient>(clientA, (provider, client) => \n",
" services.AddHttpClient<HttpClient>(clientB, (provider, client) => \n",
" client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;\n",
" .ConfigureHttpClient(client=>\n",
" client.Timeout = TimeSpan.FromSeconds(1);\n",
" client.DefaultRequestVersion = new Version(1, 1);\n",
" //name=string.Empty\n",
" //name=clientA\n",
" var httpClient_a = factory.CreateClient(clientA);\n",
" var responseA = await httpClient_a.GetAsync(\"/api/hello/ping\");\n",
" var dataA = await responseA.Content.ReadAsStringAsync();\n",
" dataA.Display();\n",
" //name=clientB\n",
" var httpClient_B = factory.CreateClient(clientB);\n",
" var responseB = await httpClient_B.GetAsync(\"/api/hello/ping\");\n",
" var dataB = await responseB.Content.ReadAsStringAsync();\n",
" dataB.Display();\n",
"### 类型化客户端 (推荐)"
"类型化的客户端,两种基本使用方式:\n",
"1、可以单独使用(直接IoC容器)\n",
"2、与IFactoryHttpClient配合使用(依赖注入),目的是:从统一的工厂配置中获取客户端,作为 HttpClient 类型的实参,传给类型化客户端的构造函数。\n",
" 换名话说:从工厂获取HttpClient实例,设置为 类型化客户端类的 HttpClient,在其内部使用。"
"// 类型化客户端 HttpClient\n",
"public class HttpClientServiceA\n",
" public HttpClient Client { get; }\n",
" public HttpClientServiceA(HttpClient client)\n",
" Client = client;\n",
" Console.WriteLine(\"HttpClientServiceA => 构造函数执行一次\");\n",
" public async Task<string> GetIndexAsync()\n",
" var response = await Client.GetAsync(\"/api/hello/index\");\n",
" return content;\n",
"public class HttpClientServiceB\n",
" public HttpClientServiceB(HttpClient client)\n",
" Console.WriteLine(\"HttpClientServiceB => 构造函数执行一次\");\n",
" public async Task<string> PingAsync()\n",
" var response = await Client.GetAsync(\"/api/hello/Ping\");\n",
"// 方式1(不推荐):类型化客户端:直接注入IoC,并从中获取实例。优点是范围可以自己选择。\n",
" Console.WriteLine(\"方式1 -------------------------------------------------------------------\");\n",
" services.AddSingleton<HttpClientServiceA>(b => \n",
" return new HttpClientServiceA(new HttpClient(){BaseAddress = new Uri(webApiBaseUrl)});\n",
" services.AddScoped<HttpClientServiceB>(b=> \n",
" return new HttpClientServiceB(new HttpClient(){BaseAddress = new Uri(webApiBaseUrl)});\n",
" var builder = services.BuildServiceProvider();\n",
" var serverA = builder.GetRequiredService<HttpClientServiceA>();\n",
" var serverB = builder.GetRequiredService<HttpClientServiceB>();\n",
" var dataA = await serverA.GetIndexAsync();\n",
" Console.WriteLine(dataA);\n",
" var dataB = await serverB.PingAsync();\n",
" Console.WriteLine(dataB);\n",
" Console.WriteLine(\"========================================================================\");\n",
"// 方式2:类型化客户端:AddHttpClient<>() 设置\n",
" Console.WriteLine(\"方式2 -------------------------------------------------------------------\");\n",
" .AddHttpClient<HttpClientServiceA>()\n",
" .AddHttpClient<HttpClientServiceB>()\n",
"// 方式3:类型化客户端:结合工厂,由工厂从统一配置中提供类型化客户端中使用的HttpClient实例。\n",
" Console.WriteLine(\"方式3 -------------------------------------------------------------------\");\n",
" services.AddHttpClient<HttpClientServiceA>(client => \n",
" Console.WriteLine(\"HttpClientServiceA => AddHttpClient 执行一次\");\n",
" .AddTypedClient<HttpClientServiceA>()\n",
" Console.WriteLine(\"HttpClientServiceA => ConfigureHttpClient 执行一次\");\n",
" services.AddHttpClient<HttpClientServiceB>(client => \n",
" Console.WriteLine(\"HttpClientServiceB => AddHttpClient 执行一次\");\n",
" .AddTypedClient<HttpClientServiceB>()\n",
" client.Timeout = TimeSpan.FromSeconds(2);\n",
" Console.WriteLine(\"HttpClientServiceB => ConfigureHttpClient 执行一次\");\n",
" var serviceA = builder.GetRequiredService<HttpClientServiceA>();\n",
" var serviceB = builder.GetRequiredService<HttpClientServiceB>();\n",
" //每获取一次类型化客户端,都会执行一交。\n",
" var serviceB2 = builder.GetRequiredService<HttpClientServiceB>();\n",
" var dataA = await serviceA.GetIndexAsync();\n",
" var dataB = await serviceB.PingAsync();\n",
" var dataB2 = await serviceB2.PingAsync();\n",
" Console.WriteLine(dataB2);\n",
"### 管道配置"
"execution_count": 150,
"outputs": [
"name": "stdout",
"output_type": "stream",
"text": [
"LoggerDelegatingHandler -> SendAsync -> Before\n",
"LoggerDelegatingHandler -> SendAsync -> After\n",
"Pong\n"
],
"//管道配置\n",
"//日志中间件(管道类)\n",
"public class LoggerDelegatingHandler : DelegatingHandler\n",
" protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)\n",
" Console.WriteLine(\"LoggerDelegatingHandler -> Send -> Before\");\n",
" HttpResponseMessage response = base.Send(request, cancellationToken);\n",
" Console.WriteLine(\"LoggerDelegatingHandler -> Send -> After\");\n",
" return response;\n",
" protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\n",
" Console.WriteLine(\"LoggerDelegatingHandler -> SendAsync -> Before\");\n",
" HttpResponseMessage response = await base.SendAsync(request, cancellationToken);\n",
" Console.WriteLine(\"LoggerDelegatingHandler -> SendAsync -> After\");\n",
"//使用日志中间件\n",
" //先注册\n",
" services.AddTransient<LoggerDelegatingHandler>();\n",
" services.AddHttpClient<HttpClient>(string.Empty).ConfigureHttpClient(client =>\n",
" //配置SocketsHttpHandler\n",
" .UseSocketsHttpHandler((handler,provider) =>\n",
" handler.ConnectTimeout = TimeSpan.FromSeconds(10);\n",
" handler.MaxConnectionsPerServer = 100;\n",
" handler.UseProxy = false;\n",
" handler.UseCookies = true;\n",
" handler.EnableMultipleHttp2Connections = true;\n",
" handler.SslOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;\n",
" //使用前先在AddTransient范围注册\n",
" //.AddHttpMessageHandler<LoggerDelegatingHandler>()\n",
" .AddHttpMessageHandler<LoggerDelegatingHandler>();\n",
" var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();\n",
" var responseString = await response.Content.ReadAsStringAsync();\n",
" Console.WriteLine(responseString);\n",
"### 日志配置"
"## 7 工厂 + Polly V8"
"## 8、综合管理:工厂 + 类型化客户端 + 请求管道 + Polly(默认使用 连接池和IoC容器)"
"kernelspec": {
"display_name": ".NET (C#)",
"language": "C#",
"name": ".net-csharp"
"language_info": {
"name": "python"
"kernelInfo": {
"defaultKernelName": "csharp",
"items": [
"aliases": [],
"name": "csharp"
"nbformat": 4,
"nbformat_minor": 2