.Net HttpClient 管理客户端(初始化与生命周期管理)
HttpClient 初始化与生命周期管理
HttpClient 旨在实例化一次,并在应用程序的整个生命周期内重复使用。
为实现复用,HttpClient类库默认使用连接池和请求管道,可以手动管理(连接池、配置管道、使用Polly); 结合IoC容器、工厂模式(提供了IHttpClientFactory类库)、复原库Polly,可以更加方便、完善的使用,这也是推荐的方法。
0、初始化与全局设置
//初始化:必须先执行一次
#!import ./ini.ipynb
1、手动管理:直接实例化-强烈不推荐
下面这种每次使用都实例化的用法是最常见、最不推荐的
:
因为HttpClient刚推出时不成熟及微软官方文档的示例代码是这种用法,再加上这种是最简单方便的使用方法,就造成很多人使用这种用法。
这种方法有如下缺点:
- 每次使用都实例化,造成性能开销大、容易内存泄露;
- 并发量大、请求频繁时:网络端口会被耗尽
Using包HttpClient,也只是在应用进程中释放了HttpClient实例,但http请求/响应是跨操作系统和网络的,而系统及网络问题在进程之上,不是进程所能处理的。
优点:
- 使用简单,好学易用;
- 并发量小且请求不频繁时,问题不大;
/*每次请求都实例化:并发量大、请求频繁进会耗尽套接字端口
*/
{ var baseUrl = WebApiConfigManager.GetWebApiConfig().BaseUrl;using(var client = new HttpClient()){//发送请求var response = await client.GetAsync(baseUrl);response.EnsureSuccessStatusCode();}//显示句柄var displayValue = display($"第 1 次请求,成功!");for(int i=0;i<10;i++){using(var client = new HttpClient()){var response = await client.GetAsync(baseUrl);response.EnsureSuccessStatusCode();displayValue.Update($"第 {i+1} 次/ 共 10 次请求,成功!");}}
}
2、手动管理:静态类或单例
相比于直接new,实现了HttpClient的重用,不推荐的
:
缺点:
- 不够灵活、优雅:特别是有多个系列的请求时;
优点:
- 复用 HttpClient
- 实现了HttpClient的重用,减少创建和销毁的开销
/*静态类/属性
*/public class HttpClientHelper
{public readonly static HttpClient StaticClient;static HttpClientHelper(){SocketsHttpHandler handler = new SocketsHttpHandler(){PooledConnectionLifetime = TimeSpan.FromSeconds(30),};StaticClient = new HttpClient(handler);//统一设置:请求头等//统一错误处理//当然这里也可以设置Pipline,不过这里就不演示了} public static async Task<HttpResponseMessage> GetAsync(string url){return await StaticClient.GetAsync(url);}public static async Task<string> GetStringAsync(string url){var response = await StaticClient.GetAsync(url);response.EnsureSuccessStatusCode();return await response.Content.ReadAsStringAsync();}public static async Task<HttpResponseMessage> PostAsync(string url, HttpContent content){return await StaticClient.PostAsync(url, content);}
}{ //调用静态类var baseUrl = WebApiConfigManager.GetWebApiConfig().BaseUrl;var response = await HttpClientHelper.GetAsync(baseUrl+"/api/Config/GetApiConfig");var content = await response.Content.ReadAsStringAsync();Console.WriteLine(content);var response2 = await HttpClientHelper.GetStringAsync(baseUrl+"/api/Normal/GetAllAccounts");Console.WriteLine(response2);
}
/*单例实现1:1. 私有构造函数,防止外部实例化2. 使用静态只读变量存储类的实例,由.Net框架保证实例不变且线程安全3. 密封类,拒绝继承,保证不被子类破坏
*/// 使用Lazy<T>实现单例
public sealed class HttpClientSingleton
{// 私有静态变量,用于存储类的实例private static readonly HttpClientSingleton instance = new HttpClientSingleton();//公共静态属性,用于获取类的实例public static HttpClientSingleton Instance{get{return instance;}}private readonly HttpClient Client;//私有构造函数,防止外部实例化private HttpClientSingleton() {SocketsHttpHandler handler = new SocketsHttpHandler(){PooledConnectionLifetime = TimeSpan.FromSeconds(30),};Client = new HttpClient(handler);//统一设置:请求头等//统一错误处理//可以使用IoC容器来管理//当然这里也可以设置Pipline,不过这里就不演示了Console.WriteLine("HttpClientSingleton 初始化一次");}public async Task<HttpResponseMessage> GetAsync(string url){return await Client.GetAsync(url);}public async Task<string> GetStringAsync(string url){var response = await Client.GetAsync(url);response.EnsureSuccessStatusCode();return await response.Content.ReadAsStringAsync();}
}{ //调用示例var baseUrl = WebApiConfigManager.GetWebApiConfig().BaseUrl;var response = await HttpClientSingleton.Instance.GetAsync(baseUrl+"/api/Config/GetApiConfig");var content = await response.Content.ReadAsStringAsync();Console.WriteLine(content);var response2 = await HttpClientSingleton.Instance.GetStringAsync(baseUrl+"/api/Normal/GetAllAccounts");Console.WriteLine(response2);
}
/*单例实现2:1. 私有构造函数,防止外部实例化2. 使用Lazy<T>, 延迟实例化, 由.Net 框架保证线程安全3. 密封类,拒绝继承,保证不被子类破坏
*/// 由于静态初始化器是由 .NET 运行时在后台处理的,因此它是线程安全的,不需要额外的锁定机制。
public sealed class HttpClientSingleton2
{private static readonly Lazy<HttpClient> _httpClientLazy = new Lazy<HttpClient>(() =>{SocketsHttpHandler handler = new SocketsHttpHandler(){PooledConnectionLifetime = TimeSpan.FromSeconds(30)};var client = new HttpClient(handler){// 可以在这里配置HttpClient的实例,例如设置超时时间、基地址等//Timeout = TimeSpan.FromSeconds(30),//BaseAddress = new Uri("https://api.example.com/"),};//统一设置:请求头等//统一错误处理//可以使用IoC容器来管理//当然这里也可以设置Pipline,不过这里就不演示了Console.WriteLine("HttpClientSingleton2 初始化一次");return client;});public static HttpClient Instance => _httpClientLazy.Value;// 私有构造函数,防止外部实例化private HttpClientSingleton2() { } public async Task<HttpResponseMessage> GetAsync(string url){return await _httpClientLazy.Value.GetAsync(url);}public async Task<string> GetStringAsync(string url){var response = await _httpClientLazy.Value.GetAsync(url);response.EnsureSuccessStatusCode();return await response.Content.ReadAsStringAsync();}
}{ //调用示例var baseUrl = WebApiConfigManager.GetWebApiConfig().BaseUrl;var response = await HttpClientSingleton2.Instance.GetAsync(baseUrl+"/api/Config/GetApiConfig");var content = await response.Content.ReadAsStringAsync();Console.WriteLine(content);var response2 = await HttpClientSingleton2.Instance.GetStringAsync(baseUrl+"/api/Normal/GetAllAccounts");Console.WriteLine(response2);
}
3、手动管理:多工具类(每类请求对应一种工具类或单例类)
把不同类别的请求分成不同的工具类,业务类直接封装成工具类的方法。类似类型化的客户端。 简单使用的话,比较推荐
优点:
- 复用HttpClient
- 可以灵活的进行统一配置
- 不同类别不同工具类,方便定制
- 业务直接封装成工具类方法,调用方便、快捷
缺点:
- 工具类比较多,需要手动维护
- 工具类方法比较多且和业务直接相关,需要手动维护
// 百度服务类
public sealed class BaiduService
{private readonly HttpClient _httpClient;public BaiduService(){//初始化httpClientvar baseHander = new SocketsHttpHandler() { MaxConnectionsPerServer = 1000 };_httpClient = new HttpClient(baseHander){Timeout = TimeSpan.FromSeconds(10),BaseAddress = new Uri("http://www.baidu.com"),};}/ <summary>/// 获取百度首页长度/// </summary>public async Task<int> GetIndexLengthAsync(string url){var response = await _httpClient.GetAsync(url);response.EnsureSuccessStatusCode();var result = await response.Content.ReadAsStringAsync();return result.Length;}
}
//调用示例
{var service = new BaiduService();var result = await service.GetIndexLengthAsync("/");Console.WriteLine(result);
}
// 本机服务类
// 百度服务类
public sealed class LocalService
{private readonly HttpClient _httpClient;public LocalService(){//初始化httpClientvar baseHander = new SocketsHttpHandler() { MaxConnectionsPerServer = 1000 };_httpClient = new HttpClient(baseHander){Timeout = TimeSpan.FromSeconds(10),BaseAddress = new Uri(WebApiConfigManager.GetWebApiConfig().BaseUrl),};}/ <summary>/// 获取百度首页长度/// </summary>public async Task<string> GetIndexAsync(string url){var response = await _httpClient.GetAsync(url);response.EnsureSuccessStatusCode();var result = await response.Content.ReadAsStringAsync();return result;}
}
//调用示例
{var service2 = new LocalService();var result = await service2.GetIndexAsync("/api/Simple/GetAccount");Console.WriteLine(result);
}
4、手动管理:可复原(Polly)请求
#r "nuget:Polly"
#r "nuget:Microsoft.Extensions.Http.Polly"using Polly;
using Polly.Simmy;
using Polly.Retry;
using Polly.Extensions;
{var pipleLine = new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions(){ShouldHandle = new PredicateBuilder().Handle<Exception>(),MaxRetryAttempts = 3, // Retry up to 3 timesOnRetry = args =>{// Due to how we have defined ShouldHandle, this delegate is called only if an exception occurred.// Note the ! sign (null-forgiving operator) at the end of the command.var exception = args.Outcome.Exception!; // The Exception property is nullableConsole.WriteLine("内部重试");return default;}}).Build();var BaseUrl = WebApiConfigManager.GetWebApiConfig().BaseUrl;HttpClient client = new HttpClient(new SocketsHttpHandler(){}){BaseAddress = new Uri(BaseUrl),};try{await pipleLine.ExecuteAsync(async (inneerToken)=>{var response = await client.GetAsync("api/Polly8/RetryException",inneerToken);response.EnsureSuccessStatusCode();});}catch(Exception ex){Console.WriteLine(ex.Message);}finally{}
}
5、IoC容器管理
直接注册IoC
/*注意:1、直接IoC管理:只能一个,不太方便;2、可使用.NET 8+ 的 KeyedService, 可以管理多个。 老版只能用服务集合,勉强能用;3、把HttpClient 放在多个类中,分别注册使用;不过这样,不如直接使用类型化客户端;
*/
{ // 直接使用var services = new ServiceCollection();services.AddSingleton<HttpClient>(new HttpClient(){//BaseAddress = new Uri("https://localhost:5001/"),Timeout = TimeSpan.FromSeconds(10),});var client = services.BuildServiceProvider().GetRequiredService<HttpClient>();var resp = await client.GetAsync("https://www.baidu.com");resp.EnsureSuccessStatusCode();var content = await resp.Content.ReadAsStringAsync();Console.WriteLine(content.Length);
}{ // KeyService: .Net 8+ 才支持的功能var services = new ServiceCollection();services.AddKeyedSingleton<HttpClient>("HttpClientA",new HttpClient(){BaseAddress = new Uri("https://www.baidu.com/"),Timeout = TimeSpan.FromSeconds(10),}).AddKeyedSingleton<HttpClient>("HttpClientB", new HttpClient(){BaseAddress = new Uri("https://www.qq.com/"),Timeout = TimeSpan.FromSeconds(2),});var clientA = services.BuildServiceProvider().GetRequiredKeyedService<HttpClient>("HttpClientA");var responseA = await clientA.GetAsync("/");responseA.EnsureSuccessStatusCode();var contentA = await responseA.Content.ReadAsStringAsync();Console.WriteLine(contentA.Length);var clientB = services.BuildServiceProvider().GetRequiredKeyedService<HttpClient>("HttpClientB");var responseB = await clientB.GetAsync("/");responseB.EnsureSuccessStatusCode();var contentB= await responseB.Content.ReadAsStringAsync();Console.WriteLine(contentB.Length);
}
HttpClient 多服务类
// IoC 多个HttpClient服务类public class HttpClientServerA
{public static HttpClient Client = new HttpClient(){BaseAddress = new Uri("https://www.baidu.com/"),Timeout = TimeSpan.FromSeconds(2),};public int GetBaiduIndexLength(){var requestMessage = new HttpRequestMessage(HttpMethod.Get, "/");var response = Client.Send(requestMessage);response.EnsureSuccessStatusCode();var s = response.Content.ReadAsStream();return (int)s.Length;}
}public class HttpClientServerB
{public static HttpClient Client = new HttpClient(){BaseAddress = new Uri("https://www.qq.com/"),Timeout = TimeSpan.FromSeconds(2),};public int GetBaiduIndexLength(){var requestMessage = new HttpRequestMessage(HttpMethod.Get, "/");var response = Client.Send(requestMessage);response.EnsureSuccessStatusCode();var s = response.Content.ReadAsStream();return (int)s.Length;}
}{var services = new ServiceCollection();services.AddScoped<HttpClientServerA>();services.AddScoped<HttpClientServerB>();var provider = services.BuildServiceProvider();var clientA = provider.GetService<HttpClientServerA>();var sumA = clientA.GetBaiduIndexLength();Console.WriteLine($"A: {sumA}");var clientB = provider.GetService<HttpClientServerB>();var sumB = clientB.GetBaiduIndexLength();Console.WriteLine($"A: {sumB}");
}
6、客户端工厂管理:IHttpClientFactory(需要结合IoC) 强力推荐
使用 IHttpClientFactory 创建和管理 短期HttpClient
是官方强力推荐的方式。特别是使用IoC或是 ASP.NET中后台调用其它接口的情况。
IHttpClientFactory 综合使用了 HttpClient的多种特性:HttpClient的生命周期、HttpClient的配置、HttpClient的拦截器、HttpClient的缓存、HttpClient的依赖注入、Polly等等。
默认客户端
从使用推测,设计 IHttpClientFactory 时,重点应该是使用 “命名客户端” 或 “类型化客户端” 而不是默认客户端。
只有 AddHttpClient() 扩展方法返回 IServiceCollection;其它相关扩展方法( AddHttpClient())均返回 IHttpClientBuilder,明显针对命名客户端。
AddHttpClient() 相当于注册了基本框架;而命名客户端中,名称为空(""或string.Empty)的,相当于默认客户端。
有一个 名为 ConfigureHttpClientDefaults
的 ServiceCollection 对象的扩展方法,用于配置所有HttpClient实例,并且只在初始化时执行一次。如果只使用一个默认客户端的话,可以使用 ConfigureHttpClientDefaults 和 AddHttpClient() 配合使用,也能达到默认客户端的配置效果。
//方式1:默认客户端
{ var services = new ServiceCollection();/*AddHttpClient() 返回 ServiceCollection,可以继续添加其他客户端。其它方法则返回IHttpClientBuilder,后结配置的扩展方法,只能针对当前前端那个命名命令端。*/services.AddHttpClient();var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();var client = factory.CreateClient();//或者var client2 = factory.CreateClient("");//或者 内部都是使用CreateClient(string.Empty),表示默认客户端。var client3 = factory.CreateClient(string.Empty);var response = await client.GetAsync(webApiBaseUrl + "/api/hello/index");response.EnsureSuccessStatusCode();var data = await response.Content.ReadAsStringAsync();data.Display();
}//方式2:默认客户端 + 默认配置
{ var services = new ServiceCollection();//默认客户端services.AddHttpClient();//配置所有客户端services.ConfigureHttpClientDefaults(builder => {//配置构建器//builder.AddDefaultLogger();//配置客户端builder.ConfigureHttpClient(c=>{c.BaseAddress = new Uri(webApiBaseUrl);});});var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();var client = factory.CreateClient();var response = await client.GetAsync("/api/hello/ping");response.EnsureSuccessStatusCode();var data = await response.Content.ReadAsStringAsync();data.Display();
}//方式3(推荐):默认客户端:直接使用名称为 string.empty 的命名客户端
{var services = new ServiceCollection();//默认客户端services.AddHttpClient<HttpClient>(string.Empty)//这样后续的配置,都是针对 string.empty 的客户端,可以使用全部配置功能.ConfigureHttpClient(c=>c.BaseAddress = new Uri(webApiBaseUrl)).AddDefaultLogger();var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();var client = factory.CreateClient();var response = await client.GetAsync("/api/hello/ping");response.EnsureSuccessStatusCode();var data = await response.Content.ReadAsStringAsync();data.Display();
}//错误用法
{var services = new ServiceCollection();//默认客户端services//没有参数时,导致后面配置不起使用;//参数必须为 空字符串或string.Empty,后续的配置才能起使用.AddHttpClient<HttpClient>()//没有参数时,导致后面配置不起使用.ConfigureHttpClient(c=>c.BaseAddress = new Uri(webApiBaseUrl)).AddDefaultLogger();var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();var client = factory.CreateClient();try{var response = await client.GetAsync("/api/hello/ping");response.EnsureSuccessStatusCode();var data = await response.Content.ReadAsStringAsync();data.Display();}catch(InvalidOperationException ex){Console.WriteLine($"没有参数的配置:AddHttpClient<HttpClient>(),因后续配置中,赋值 BaseAddress 不起使用,出现异常:{Environment.NewLine}{ex.Message}");}catch(Exception ex){Console.WriteLine(ex.Message);}finally{client.Dispose();}
}
默认全局配置
ConfigureHttpClientDefaults 扩展方法,添加一个委托,用于配置所有HttpClient实例。只执行一次。
//全局配置:所有HttpClient配置
{var services = new ServiceCollection();//添加一个委托,用于配置所有HttpClient实例。//只执行一次,而非每次CreateClient,都会执行一次。services.ConfigureHttpClientDefaults(builder => {//builder.UseSocketsHttpHandler();//builder.SetHandlerLifetime(TimeSpan.FromMinutes(5));builder.ConfigureHttpClient(hc =>{hc.BaseAddress = new Uri(webApiBaseUrl);});Console.WriteLine("ConfigureHttpClientDefaults 只执行一次!");});//配置命名客户端services.AddHttpClient<HttpClient>("client_a").ConfigureHttpClient(hc => {hc.DefaultRequestHeaders.Add("client_a", "client_a");//可以覆盖默认配置//hc.BaseAddress = new Uri("http://www.qq.com");Console.WriteLine("ConfigureHttpClient 每次 CreateClient 执行一次!");});var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();//默认客户端var defaultClient = factory.CreateClient();var defaultResponse = await defaultClient.GetAsync("/api/hello/ping");var defaultData = await defaultResponse.Content.ReadAsStringAsync();Console.WriteLine(defaultData);//命名客户端var namedClient = factory.CreateClient("client_a");var namedResponse = await namedClient.GetAsync("/api/hello/get");var namedData = await namedResponse.Content.ReadAsStringAsync();Console.WriteLine(namedData);_ = factory.CreateClient("client_a");
}
命名客户端(推荐用法)
命名客户端,应该是官方推荐的方法。名称为空字符串或string.Empty时,可以为是默认命名客户端,factory.CreateClient()创建的就是这个默认客户端(或者factory.CreateClient(“”))。
//命名客户端
{var clientA ="httpClientA";var clientB ="httpClientB";var services = new ServiceCollection();services.AddHttpClient<HttpClient>(string.Empty, (provider, client) => {client.BaseAddress = new Uri(webApiBaseUrl);});services.AddHttpClient<HttpClient>(clientA, (provider, client) => {client.BaseAddress = new Uri(webApiBaseUrl);});services.AddHttpClient<HttpClient>(clientB, (provider, client) => {client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;client.BaseAddress = new Uri(webApiBaseUrl);}).ConfigureHttpClient(client=>{client.Timeout = TimeSpan.FromSeconds(1);client.DefaultRequestVersion = new Version(1, 1);});var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();//name=string.Emptyvar defaultClient = factory.CreateClient();var defaultResponse = await defaultClient.GetAsync("/api/hello/ping");var defaultData = await defaultResponse.Content.ReadAsStringAsync();Console.WriteLine(defaultData);//name=clientAvar httpClient_a = factory.CreateClient(clientA);var responseA = await httpClient_a.GetAsync("/api/hello/ping");var dataA = await responseA.Content.ReadAsStringAsync();dataA.Display();//name=clientBvar httpClient_B = factory.CreateClient(clientB);var responseB = await httpClient_B.GetAsync("/api/hello/ping");var dataB = await responseB.Content.ReadAsStringAsync();dataB.Display();
}
类型化客户端 (推荐)
类型化的客户端,两种基本使用方式:
1、可以单独使用(直接IoC容器)
2、与IFactoryHttpClient配合使用(依赖注入),目的是:从统一的工厂配置中获取客户端,作为 HttpClient 类型的实参,传给类型化客户端的构造函数。
换名话说:从工厂获取HttpClient实例,设置为 类型化客户端类的 HttpClient,在其内部使用。
// 类型化客户端 HttpClient
public class HttpClientServiceA
{public HttpClient Client { get; }public HttpClientServiceA(HttpClient client){Client = client;Console.WriteLine("HttpClientServiceA => 构造函数执行一次");}public async Task<string> GetIndexAsync(){var response = await Client.GetAsync("/api/hello/index");var content = await response.Content.ReadAsStringAsync();return content;}
}public class HttpClientServiceB
{public HttpClient Client { get; }public HttpClientServiceB(HttpClient client){Client = client;Console.WriteLine("HttpClientServiceB => 构造函数执行一次");}public async Task<string> PingAsync(){var response = await Client.GetAsync("/api/hello/Ping");var content = await response.Content.ReadAsStringAsync();return content;}
}// 方式1(不推荐):类型化客户端:直接注入IoC,并从中获取实例。优点是范围可以自己选择。
{Console.WriteLine("方式1 -------------------------------------------------------------------");var services = new ServiceCollection();services.AddSingleton<HttpClientServiceA>(b => { return new HttpClientServiceA(new HttpClient(){BaseAddress = new Uri(webApiBaseUrl)});});services.AddScoped<HttpClientServiceB>(b=> {return new HttpClientServiceB(new HttpClient(){BaseAddress = new Uri(webApiBaseUrl)});});var builder = services.BuildServiceProvider();var serverA = builder.GetRequiredService<HttpClientServiceA>();var serverB = builder.GetRequiredService<HttpClientServiceB>();var dataA = await serverA.GetIndexAsync();Console.WriteLine(dataA);var dataB = await serverB.PingAsync();Console.WriteLine(dataB);Console.WriteLine("========================================================================");
}// 方式2:类型化客户端:AddHttpClient<>() 设置
{Console.WriteLine("方式2 -------------------------------------------------------------------");var services = new ServiceCollection();services.AddHttpClient<HttpClientServiceA>().ConfigureHttpClient(client=>{client.BaseAddress = new Uri(webApiBaseUrl);});services.AddHttpClient<HttpClientServiceB>().ConfigureHttpClient(client=>{client.BaseAddress = new Uri(webApiBaseUrl);});var builder = services.BuildServiceProvider();var serverA = builder.GetRequiredService<HttpClientServiceA>();var serverB = builder.GetRequiredService<HttpClientServiceB>();var dataA = await serverA.GetIndexAsync();Console.WriteLine(dataA);var dataB = await serverB.PingAsync();Console.WriteLine(dataB);Console.WriteLine("========================================================================");
}// 方式3:类型化客户端:结合工厂,由工厂从统一配置中提供类型化客户端中使用的HttpClient实例。
{Console.WriteLine("方式3 -------------------------------------------------------------------");var services = new ServiceCollection();services.AddHttpClient<HttpClientServiceA>(client => {client.BaseAddress = new Uri(webApiBaseUrl);Console.WriteLine("HttpClientServiceA => AddHttpClient 执行一次");}).AddTypedClient<HttpClientServiceA>().ConfigureHttpClient(client=>{client.Timeout = TimeSpan.FromSeconds(1);Console.WriteLine("HttpClientServiceA => ConfigureHttpClient 执行一次");});services.AddHttpClient<HttpClientServiceB>(client => {client.BaseAddress = new Uri(webApiBaseUrl);Console.WriteLine("HttpClientServiceB => AddHttpClient 执行一次");}).AddTypedClient<HttpClientServiceB>().ConfigureHttpClient(client=>{client.Timeout = TimeSpan.FromSeconds(2);Console.WriteLine("HttpClientServiceB => ConfigureHttpClient 执行一次");});var builder = services.BuildServiceProvider();var serviceA = builder.GetRequiredService<HttpClientServiceA>();var serviceB = builder.GetRequiredService<HttpClientServiceB>();//每获取一次类型化客户端,都会执行一交。var serviceB2 = builder.GetRequiredService<HttpClientServiceB>();var dataA = await serviceA.GetIndexAsync();Console.WriteLine(dataA);var dataB = await serviceB.PingAsync();Console.WriteLine(dataB);var dataB2 = await serviceB2.PingAsync();Console.WriteLine(dataB2);Console.WriteLine("========================================================================");
}
管道配置
//管道配置//日志中间件(管道类)
public class LoggerDelegatingHandler : DelegatingHandler
{protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken){Console.WriteLine("LoggerDelegatingHandler -> Send -> Before");HttpResponseMessage response = base.Send(request, cancellationToken);Console.WriteLine("LoggerDelegatingHandler -> Send -> After");return response;}protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){Console.WriteLine("LoggerDelegatingHandler -> SendAsync -> Before");HttpResponseMessage response = await base.SendAsync(request, cancellationToken);Console.WriteLine("LoggerDelegatingHandler -> SendAsync -> After");return response;}
}//使用日志中间件
{var services = new ServiceCollection();//先注册services.AddTransient<LoggerDelegatingHandler>();services.AddHttpClient<HttpClient>(string.Empty).ConfigureHttpClient(client =>{client.BaseAddress = new Uri(webApiBaseUrl);})//配置SocketsHttpHandler.UseSocketsHttpHandler((handler,provider) =>{handler.ConnectTimeout = TimeSpan.FromSeconds(10);handler.MaxConnectionsPerServer = 100;handler.UseProxy = false;handler.UseCookies = true;handler.EnableMultipleHttp2Connections = true;handler.SslOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;})//使用前先在AddTransient范围注册.AddHttpMessageHandler<LoggerDelegatingHandler>();var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();var client = factory.CreateClient();var response = await client.GetAsync("/api/hello/ping");response.EnsureSuccessStatusCode();var responseString = await response.Content.ReadAsStringAsync();Console.WriteLine(responseString);
}
日志配置
默认日志配置,需要先引用 Microsoft.Extensions.Logging
和 Microsoft.Extensions.Logging.Console
包,进行通用日志配置!
//通用日志
{ILoggerFactory loggerFactory = LoggerFactory.Create(buider =>{buider.AddConsole();});ILogger logger = loggerFactory.CreateLogger("logger");logger.LogInformation("直接使用的通用日志!");
}//IoC中使用
{var services = new ServiceCollection();services.AddLogging(config =>{config.SetMinimumLevel(LogLevel.Information);config.AddConsole();//config.AddSimpleConsole();//config.AddSystemdConsole();});var serviceProvider = services.BuildServiceProvider();var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();var logger = loggerFactory.CreateLogger("logger");logger.LogInformation("IoC中使用日志!");logger.LogError("IoC中的错误日志!");
}
配置默认日志
//配置默认日志(必须有常规日志及级别设置,否则不起使用)
{var services = new ServiceCollection();// 1、配置通用日志services.AddLogging(config =>{//日志级别config.SetMinimumLevel(LogLevel.Trace);//config.SetMinimumLevel(LogLevel.Information);//日志载体config.AddConsole();//config.AddDebug();//config.AddJsonConsole();//config.AddSimpleConsole();//config.AddSystemdConsole();});services.ConfigureHttpClientDefaults(options =>{//2、配置通用日志options.AddDefaultLogger();}).AddHttpClient<HttpClient>(String.Empty,c =>{c.BaseAddress = new Uri(webApiBaseUrl);c.DefaultRequestHeaders.Add("Authorization", "Bearer a.b.c");})//2、或者单独配置此命名客户端日志.AddDefaultLogger();var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();var client = factory.CreateClient(String.Empty);var response = await client.GetAsync("api/hello/index");response.EnsureSuccessStatusCode();var content = await response.Content.ReadAsStringAsync();Console.WriteLine(content);
}
配置自定义日志
博客 可以参考
/* 添加自定义日志记录1、可以指定当 HttpClient 启动请求、接收响应或引发异常时记录的内容和方式。可以同时添加多个自定义记录器(控制台、ETW 记录器),或“包装”和“不包装”记录器。由于其附加性质,可能需要事先显式删除默认的“旧”日志记录。要添加自定义日志记录,您需要实现 IHttpClientLogger 接口,然后使用 AddLogger 将自定义记录器添加到客户端。请注意,日志记录实现不应引发任何异常,否则可能会中断请求执行2、请求上下文对象上下文对象可用于将 LogRequestStart 调用与相应的 LogRequestStop 调用相匹配,以将数据从一个调用传递到另一个调用。 Context 对象由 LogRequestStart 生成,然后传递回 LogRequestStop。这可以是属性包或保存必要数据的任何其他对象。如果不需要上下文对象,实现可以从 LogRequestStart 返回 null。3、避免从内容流中读取例如,如果您打算阅读和记录请求和响应内容,请注意,它可能会对最终用户体验产生不利的副作用并导致错误。例如,请求内容可能在发送之前被消耗,或者巨大的响应内容可能最终被缓冲在内存中。此外,在 .NET 7 之前,访问标头不是线程安全的,可能会导致错误和意外行为。4、谨慎使用异步日志记录我们期望同步 IHttpClientLogger 接口适用于绝大多数自定义日志记录用例。出于性能原因,建议不要在日志记录中使用异步。但是,如果严格要求日志记录中的异步访问,您可以实现异步版本 IHttpClientAsyncLogger。它派生自 IHttpClientLogger,因此可以使用相同的 AddLogger API 进行注册。请注意,在这种情况下,还应该实现日志记录方法的同步对应项,特别是如果该实现是面向 .NET Standard 或 .NET 5+ 的库的一部分。同步对应项是从同步 HttpClient.Send 方法调用的;即使 .NET Standard 表面不包含它们,.NET Standard 库也可以在 .NET 5+ 应用程序中使用,因此最终用户可以访问同步 HttpClient.Send 方法。5、包装和不包装记录仪:当您添加记录器时,您可以显式设置wrapHandlersPipeline参数来指定记录器是否将被包装。默认不包装。在将重试处理程序添加到管道的情况下(例如 Polly 或某些重试的自定义实现),包装和不包装管道之间的区别最为显着。
*/// 创建一个简单的控制台日志类
public class SimpleConsoleLogger : IHttpClientLogger
{public object? LogRequestStart(HttpRequestMessage request){return null;}public void LogRequestStop(object? ctx, HttpRequestMessage request, HttpResponseMessage response, TimeSpan elapsed){Console.WriteLine($"自定义日志:{request.Method} {request.RequestUri?.AbsoluteUri} - {(int)response.StatusCode} {response.StatusCode} in {elapsed.TotalMilliseconds}ms");}public void LogRequestFailed(object? ctx, HttpRequestMessage request, HttpResponseMessage? response, Exception e, TimeSpan elapsed){Console.WriteLine($"自定义日志:{request.Method} {request.RequestUri?.AbsoluteUri} - Exception {e.GetType().FullName}: {e.Message}");}
}//使用
{var services = new ServiceCollection();//1、先注册日志类services.AddSingleton<SimpleConsoleLogger>();services// 全局配置.ConfigureHttpClientDefaults(options =>{})// 配置到HttpClient.AddHttpClient<HttpClient>(String.Empty,c =>{c.BaseAddress = new Uri(webApiBaseUrl);})//可选:取消默认日志记录.RemoveAllLoggers()//2、配置到HttpClient.AddLogger<SimpleConsoleLogger>();var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();var client = factory.CreateClient(String.Empty);var response = await client.GetAsync("api/hello/index");response.EnsureSuccessStatusCode();var content = await response.Content.ReadAsStringAsync();Console.WriteLine($"API 影响内容:{content}");
}// 使用上下文的日志类
public class RequestIdLogger : IHttpClientLogger
{private readonly ILogger _log;public RequestIdLogger(ILogger<RequestIdLogger> log){_log = log;}private static readonly Action<ILogger, Guid, string?, Exception?> _requestStart = LoggerMessage.Define<Guid, string?>(LogLevel.Information,EventIds.RequestStart,"Request Id={RequestId} ({Host}) started");private static readonly Action<ILogger, Guid, double, Exception?> _requestStop = LoggerMessage.Define<Guid, double>(LogLevel.Information,EventIds.RequestStop,"Request Id={RequestId} succeeded in {elapsed}ms");private static readonly Action<ILogger, Guid, Exception?> _requestFailed = LoggerMessage.Define<Guid>(LogLevel.Error,EventIds.RequestFailed,"Request Id={RequestId} FAILED");public object? LogRequestStart(HttpRequestMessage request){var ctx = new Context(Guid.NewGuid());_requestStart(_log, ctx.RequestId, request.RequestUri?.Host, null);return ctx;}public void LogRequestStop(object? ctx, HttpRequestMessage request, HttpResponseMessage response, TimeSpan elapsed){_requestStop(_log, ((Context)ctx!).RequestId, elapsed.TotalMilliseconds, null);}public void LogRequestFailed(object? ctx, HttpRequestMessage request, HttpResponseMessage? response, Exception e, TimeSpan elapsed){_requestFailed(_log, ((Context)ctx!).RequestId, null);}public static class EventIds{public static readonly EventId RequestStart = new(1, "RequestStart");public static readonly EventId RequestStop = new(2, "RequestStop");public static readonly EventId RequestFailed = new(3, "RequestFailed");}record Context(Guid RequestId);
}//使用
{var services = new ServiceCollection();services.AddLogging(config =>{config.SetMinimumLevel(LogLevel.Trace);config.AddConsole();});//1、先注册日志类services.AddSingleton<RequestIdLogger>();services// 全局配置.ConfigureHttpClientDefaults(options =>{})// 配置到HttpClient.AddHttpClient<HttpClient>(String.Empty,c =>{c.BaseAddress = new Uri(webApiBaseUrl);})//可选:取消默认日志记录.RemoveAllLoggers()//2、配置到HttpClient.AddLogger<RequestIdLogger>();var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();var client = factory.CreateClient(String.Empty);var response = await client.GetAsync("api/hello/get");response.EnsureSuccessStatusCode();var content = await response.Content.ReadAsStringAsync();Console.WriteLine($"API 影响内容:{content}");
}
7 工厂 + Polly V8
IFactoryHttpClient 与 Polly配合,可轻松实现重试、熔断、降级、限流等功能,本文只是简略的给出常用的使用方法,详情会写在 Polly学习项目中。Polly 官方参考
使用步骤:
- 引用 Polly v8 和 Microsoft.Extensions.Http.Polly 包
- 配置命名客户端
- 使用 AddTransientHttpErrorPolicy 快捷方法,配置策略
- 使用其它方式配置,并且可以使用多策略、注册策略、上下文等功能
基础应用
使用快捷方法AddTransientHttpErrorPolicy,进行常用功能使用。
/*便捷应用:AddTransientHttpErrorPolicy() 方法,添加常用瞬时错误重试策略
*/
{var services = new ServiceCollection();services.AddHttpClient(string.Empty)//配置默认命名客户端.ConfigureHttpClient(client => {client.BaseAddress = new Uri(webApiBaseUrl);})//设置Policy错误处理快捷扩展方法.AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(new[]{TimeSpan.FromSeconds(1),TimeSpan.FromSeconds(2),TimeSpan.FromSeconds(4),}))//可以多次调用:设置多个策略.AddTransientHttpErrorPolicy(builder => builder.RetryAsync(1));var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();var content = await factory.CreateClient().GetStringAsync("/api/polly8/RandomException");Console.WriteLine($"响应内容:{content}");
}
使用通过传统 Polly 语法配置的任何策略
使用 AddPolicyHandler 方法及其重载也可用于接受任何 IAsyncPolicy ,因此可以定义和应用任何类型的策略:可以指定要处理的内容和处理方式。
/*传统方式配置Polly策略
*/
//创建策略
{var services = new ServiceCollection();//重试策略var retryePolicy = Policy.Handle<HttpRequestException>().OrResult<HttpResponseMessage>(response => {return response.StatusCode == System.Net.HttpStatusCode.Created;}).WaitAndRetryAsync(new TimeSpan[]{TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(2)});//调用services.AddHttpClient(string.Empty).AddPolicyHandler(retryePolicy);//超时策略var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(10);services.AddHttpClient("timeoutPolicy").AddPolicyHandler(timeoutPolicy);/* 普通策略转换所有通过 HttpClient 的调用都返回 HttpResponseMessage 因此配置的策略必须是 IAsyncPolicy<HttpResponseMessage> 通过简单、便捷的 AsAsyncPolicy<HttpResponseMessage>()方法,将非通用策略 IAsyncPolicy 转换为 IAsyncPolicy<HttpResponseMessage> */var timeoutPolicy2 = Policy.TimeoutAsync(2);services.AddHttpClient("timeoutPolicy2")//AsAsyncPolicy转换通用策略.AddPolicyHandler(timeoutPolicy2.AsAsyncPolicy<HttpResponseMessage>());
}//示例
{//创建策略var policy = Policy.RateLimitAsync<HttpResponseMessage>(3,TimeSpan.FromSeconds(10));//使用var services = new ServiceCollection();services.AddHttpClient(string.Empty).ConfigureHttpClient(client => {client.BaseAddress = new Uri(webApiBaseUrl);}).AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(new[]{TimeSpan.FromSeconds(1),TimeSpan.FromSeconds(2),TimeSpan.FromSeconds(4)})).AddPolicyHandler(policy);try{var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();var content = await factory.CreateClient().GetStringAsync("/api/polly8/RandomException");Console.WriteLine($"响应内容:{content}");}catch(Exception ex){Console.WriteLine($"未处理的异常:{ex.Message}");}
}
应用多个策略
{var services = new ServiceCollection();services.AddHttpClient(string.Empty).ConfigureHttpClient(client => {client.BaseAddress = new Uri(webApiBaseUrl);}).AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(new[]{TimeSpan.FromSeconds(1),TimeSpan.FromSeconds(2),TimeSpan.FromSeconds(3),}))//断路器.AddTransientHttpErrorPolicy(builder => builder.CircuitBreakerAsync(handledEventsAllowedBeforeBreaking: 3,durationOfBreak: TimeSpan.FromSeconds(30)));try{var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();var content = await factory.CreateClient().GetStringAsync("/api/polly8/RandomException");Console.WriteLine(content);}catch(Exception ex){Console.WriteLine("API异常:"+ex.Message);}
}
动态选择策略
//实质是AddPolicyHandler中选择一个策略
{var retryPolicy = Polly.Extensions.Http.HttpPolicyExtensions.HandleTransientHttpError().WaitAndRetryAsync(new[]{TimeSpan.FromSeconds(1),TimeSpan.FromSeconds(2),TimeSpan.FromSeconds(4)});var noOpPolicy = Policy.NoOpAsync().AsAsyncPolicy<HttpResponseMessage>();var services = new ServiceCollection();services.AddHttpClient(string.Empty, client =>{client.BaseAddress = new Uri(webApiBaseUrl);})// 根据请求方法,选择策略.AddPolicyHandler(request => request.Method == HttpMethod.Get ? retryPolicy : noOpPolicy);var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();var client1 = factory.CreateClient(string.Empty);var content1 = await client1.GetStringAsync("/api/hello/get");Console.WriteLine(content1);var client2 = factory.CreateClient(string.Empty);var response2 = await client2.PostAsync("/api/hello/post",null);var content2 = await response2.Content.ReadAsStringAsync();Console.WriteLine(content2);
}
从注册表中选择策略
{var registry = new PolicyRegistry(){{ "defaultretrystrategy", HttpPolicyExtensions.HandleTransientHttpError().WaitAndRetryAsync(new TimeSpan[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3)}) },{ "defaultcircuitbreaker", HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)) },};var services = new ServiceCollection();services.AddPolicyRegistry(registry);services.AddHttpClient("a", client => { client.BaseAddress = new Uri(webApiBaseUrl); }).AddPolicyHandlerFromRegistry("defaultretrystrategy")//.AddPolicyHandlerFromRegistry("defaultcircuitbreaker");services.AddHttpClient("b", client => { client.BaseAddress = new Uri(webApiBaseUrl); })//.AddPolicyHandlerFromRegistry("defaultretrystrategy").AddPolicyHandlerFromRegistry("defaultcircuitbreaker");var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();var clientA = factory.CreateClient("a");var clientB = factory.CreateClient("b");try{var resultA = await clientA.GetStringAsync("/api/polly8/exception");}catch (Exception ex){Console.WriteLine(ex.Message);}var resultB = await clientB.GetStringAsync("/api/polly8/hello");
}
8、综合管理:工厂 + 类型化客户端 + 请求管道 + Polly(默认使用 连接池和IoC容器)
综合示例1
/* 综合示例1工厂 + 类型化客户端 + 管道 + Polly + 日志(自定义)
*///类型化客户端
public class HelloApiService
{public HttpClient Client { get; set; }public HelloApiService(HttpClient httpClient){Client = httpClient;}public async Task<string> Ping(){var content = await Client.GetStringAsync("/api/Hello/Ping");return content;}public async Task<string> Index(){var content = await Client.GetStringAsync("/api/Hello/Index");return content;}public async Task<string> Get(){var content = await Client.GetStringAsync("/api/Hello/Get");return content;}public async Task<string> Post(){var response = await Client.PostAsync("/api/Hello/Post", null);var content = await response.Content.ReadAsStringAsync();return content;}
}//类型化客户端
public class Polly8ApiService
{public HttpClient Client { get; set; }public Polly8ApiService(HttpClient httpClient){Client = httpClient;} public async Task<string> Hello(){var content = await Client.GetStringAsync("/api/Polly8/Hello");return content;}public async Task<string> Exception(){var response = await Client.GetAsync("/api/Polly8/Exception");response.EnsureSuccessStatusCode();var content = await response.Content.ReadAsStringAsync();return content;}public async Task<string> RetryException(){var response = await Client.GetAsync("/api/Polly8/RetryException");response.EnsureSuccessStatusCode();var content = await response.Content.ReadAsStringAsync();return content;}public async Task<string> RandomException(){var response = await Client.GetAsync("/api/Polly8/RandomException");response.EnsureSuccessStatusCode();var content = await response.Content.ReadAsStringAsync();return content;}public async Task<string> ToggleException(){var response = await Client.GetAsync("/api/Polly8/ToggleException?toggleId="+Guid.NewGuid().ToString());response.EnsureSuccessStatusCode();var content = await response.Content.ReadAsStringAsync();return content;}
}//Token管理中间件
public class TokenDelegatingHandler : DelegatingHandler
{protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken){Console.WriteLine("TokenDelegatingHandler -> Send -> Added Token");if (!request.Headers.Contains(Microsoft.Net.Http.Headers.HeaderNames.Authorization)) {Console.WriteLine("没有 Token, TokenDelegatingHandler 添加之");request.Headers.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, "Bearer " + "a.b.c");}else{Console.WriteLine($"已有Token, {request.Headers.Authorization}");}HttpResponseMessage response = base.Send(request, cancellationToken);Console.WriteLine("TokenDelegatingHandler -> Send -> After");return response;}protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){Console.WriteLine("TokenDelegatingHandler -> SendAsync -> Before");HttpResponseMessage response = await base.SendAsync(request, cancellationToken);Console.WriteLine("TokenDelegatingHandler -> SendAsync -> After");return response;}
}//自定义日志
public class CustomLogger : IHttpClientLogger
{public object? LogRequestStart(HttpRequestMessage request){return null;}public void LogRequestStop(object? ctx, HttpRequestMessage request, HttpResponseMessage response, TimeSpan elapsed){Console.WriteLine($"自定义日志:{request.Method} {request.RequestUri?.AbsoluteUri} - {(int)response.StatusCode} {response.StatusCode} in {elapsed.TotalMilliseconds}ms");}public void LogRequestFailed(object? ctx, HttpRequestMessage request, HttpResponseMessage? response, Exception e, TimeSpan elapsed){Console.WriteLine($"自定义日志:{request.Method} {request.RequestUri?.AbsoluteUri} - Exception {e.GetType().FullName}: {e.Message}");}
}//polly策略
var policy = Policy.Handle<HttpRequestException>().OrResult<HttpResponseMessage>(message => message.StatusCode != System.Net.HttpStatusCode.OK).WaitAndRetryAsync(new TimeSpan[]{TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2),TimeSpan.FromSeconds(4),});//使用
{var services = new ServiceCollection();//注册基础类型services//注册日志类.AddTransient<CustomLogger>().AddScoped<TokenDelegatingHandler>();//基础配置services// 基础日志配置(默认日志).AddLogging(builder => {//日志级别builder.SetMinimumLevel(LogLevel.Trace);//控制台日志builder.AddConsole();})//全局配置.ConfigureHttpClientDefaults(clientBuilder =>{clientBuilder.AddDefaultLogger();clientBuilder.ConfigureHttpClient(client => {client.BaseAddress = new Uri(webApiBaseUrl);});});//默认命名客户端services.AddHttpClient<HttpClient>(string.Empty, config => {config.DefaultRequestHeaders.Add("X-Custom-Demo", "true");})//配置客户端.ConfigureHttpClient(client => {//client.BaseAddress = new Uri(webApiBaseUrl);client.Timeout = TimeSpan.FromSeconds(10);})//添加类型化客户端.AddTypedClient<HelloApiService>()//添加自定义管道.AddHttpMessageHandler<TokenDelegatingHandler>()//添加默认日志:全局配置已添加//.AddDefaultLogger()//添加自定义日志.AddLogger<CustomLogger>()//日志转发头(所有请求头).RedactLoggedHeaders( headerName => true)//配置SocketsHttpHandler.UseSocketsHttpHandler(config =>{//配置连接池等config.Configure((handler,provider) => {handler.AllowAutoRedirect = true;handler.PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30);handler.PooledConnectionLifetime = TimeSpan.FromSeconds(30);handler.UseProxy = false;handler.UseCookies = true;});})//设置生命周期.SetHandlerLifetime(TimeSpan.FromSeconds(30))//Polly策略配置.AddPolicyHandler(policy)//便捷配置.AddTransientHttpErrorPolicy(builder => builder.CircuitBreakerAsync<HttpResponseMessage>(11, TimeSpan.FromSeconds(30)));//自定义services.AddHttpClient<HttpClient>("ClientA", config => {config.DefaultRequestHeaders.Add("X-Custom-Demo", "ClientA");})//配置客户端.ConfigureHttpClient(client => {//client.BaseAddress = new Uri(webApiBaseUrl);client.Timeout = TimeSpan.FromSeconds(10);})//添加类型化客户端.AddTypedClient<Polly8ApiService>()//添加自定义管道.AddHttpMessageHandler<TokenDelegatingHandler>()//添加默认日志:全局配置已添加//.AddDefaultLogger()//添加自定义日志.AddLogger<CustomLogger>()//日志转发头(所有请求头).RedactLoggedHeaders( headerName => true)//配置SocketsHttpHandler.UseSocketsHttpHandler(config =>{//配置连接池等config.Configure((handler,provider) => {handler.AllowAutoRedirect = true;handler.PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30);handler.PooledConnectionLifetime = TimeSpan.FromSeconds(30);handler.UseProxy = false;handler.UseCookies = true;});})//设置生命周期.SetHandlerLifetime(TimeSpan.FromSeconds(30))//Polly策略配置.AddPolicyHandler(policy)//便捷配置.AddTransientHttpErrorPolicy(builder => builder.CircuitBreakerAsync<HttpResponseMessage>(11, TimeSpan.FromSeconds(30)));var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();var defaultClient = factory.CreateClient();var defaultContent = await defaultClient.GetStringAsync("api/hello/ping");Console.WriteLine(defaultContent);var clientA = factory.CreateClient();var contentA = await clientA.GetStringAsync("api/polly8/hello");Console.WriteLine(contentA);//类型化客户端HelloApiService helloApiService = services.BuildServiceProvider().GetRequiredService<HelloApiService>();Console.WriteLine(await helloApiService.Ping());Console.WriteLine(await helloApiService.Index());Console.WriteLine(await helloApiService.Get());Console.WriteLine(await helloApiService.Post());Polly8ApiService polly8ApiService = services.BuildServiceProvider().GetRequiredService<Polly8ApiService>();Console.WriteLine(await polly8ApiService.Hello());}