.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());}
相关文章:
.Net HttpClient 管理客户端(初始化与生命周期管理)
HttpClient 初始化与生命周期管理 HttpClient 旨在实例化一次,并在应用程序的整个生命周期内重复使用。 为实现复用,HttpClient类库默认使用连接池和请求管道,可以手动管理(连接池、配置管道、使用Polly); 结合IoC容器、工厂模式(提供了IHt…...
树莓派4的v4l2摄像头(csi)no cameras available,完美解决
根据2025年最新技术文档和树莓派官方支持建议,no cameras available错误通常由驱动配置冲突或硬件连接问题导致。以下是系统化解决方案: 一、核心修复步骤 强制禁用传统驱动 sudo nano /boot/firmware/config.txt确保包含以下配置(2025年新版…...
VBA将PDF文档内容逐行写入Excel
VBA是无法直接读取PDF文档的,但结合上期我给大家介绍了PDF转换工具xpdf-tools-4.05,先利用它将PDF文档转换为TXT文档,然后再将TXT的内容写入Excel,这样就间接实现了将PDF文档的内容导入Excel的操作。下面的代码将向大家演示如何实…...
【STM32 学习笔记】USART串口
注意:在串口助手的接收模式中有文本模式和HEX模式两种模式,那么它们有什么区别? 文本模式和Hex模式是两种不同的文件编辑或浏览模式,不是完全相同的概念。文本模式通常是指以ASCII编码格式表示文本文件的编辑或浏览模式。在文…...
位图布隆过滤器
1.位图 所谓位图,就是用每一位来存放某种状态,适用于海量数据,整数,数据无重复的场景。通常是用来判 断某个数据存不存在的。 如上例子,10个整数本应该存放需要40个字节,此时用位图只需要3个字节。 下面代…...
【Web】使用Vue3开发鸿蒙的HelloWorld!
文章目录 1、简介2、效果3、环境3.1、开发环境3.2、运行环境 4、实现4.1、在VSCode上使用Vue开发HelloWorld4.1.1、通过 Vite 快速创建项目4.1.2、修改 src/App.vue4.1.3、模拟Web浏览器运行 4.2、使用DevEco完成手机App端移植4.2.1、构建Vue 3项目为静态文件4.2.2、创建Harmon…...
cv_area_center()
主题 用opencv实现了halcon中area_center算子的功能, 返回region的面积,中心的行坐标和中心的列坐标 代码很简单 def cv_area_center(region):area[]row []col []for re in region:retval cv2.moments(re)area.append(retval[m00])row.append(int(r…...
Python+OpenCV实现手势识别与动作捕捉:技术解析与应用探索
引言:人机交互的新维度 在人工智能与计算机视觉技术飞速发展的今天,手势识别与动作捕捉技术正逐步从实验室走向大众生活。通过Python的OpenCV库及MediaPipe等工具,开发者能够以较低门槛实现精准的手部动作识别,为虚拟现实、智能家…...
【llama-factory】Lora微调和DPO训练
微调参考 DPO参考 llama-factory官网 LoRA微调 数据集处理 数据集格式和注册 Alpaca数据集格式: [{"instruction": "人类指令(必填)","input": "人类输入(选填)","…...
JS较底层的用法,几类简单介绍
JS较底层的用法 在 JavaScript 中,“偏底层”的用法通常是指更接近语言核心、规范、底层机制的特性。这些用法不是日常开发中最常见的,但对理解语言原理、优化性能或构建框架、库非常重要。下面是一些常见的“偏底层”用法或特性 1. 对象属性底层操作&am…...
当可视化遇上 CesiumJS:突破传统,打造前沿生产配套方案
CesiumJS 技术基础介绍 CesiumJS 是一款基于 JavaScript 的开源库,专门用于创建动态、交互式的地理空间可视化。它利用 WebGL 技术,能够在网页浏览器中流畅地渲染高分辨率的三维地球和地图场景。CesiumJS 支持多种地理空间数据格式,包括但不…...
使用python脚本连接SQL Server数据库导出表结构
一. 准备工作 Mac 系统安装freetds brew install freetds 安装pymssql pip3 install pymssql 二.导出指定表的结构: import pymssql# 配置数据库连接参数(根据实际情况修改) server # 内网服务器地址或IP database # 数据库名称…...
Docker基础入门
Docker核心概念 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。 容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app&#…...
day011-权限管理专题
文章目录 1. 对比文件内容1.1 diff1.2 vimdiff 2. /etc/skel目录3. 权限基础4. 修改权限4.1 用数字权限修改4.2 用字母修改权限(ugo)4.3 修改文件所有者和用户组 5. 文件与目录权限6. permission denied 权限拒绝7. 特殊权限8. 特殊属性9. 思维导图 1. 对…...
ragflow报错:KeyError: ‘\n “序号“‘
环境: ragflowv 0.17.2 问题描述: ragflow报错:KeyError: ‘\n “序号”’ **1. 推荐表(输出json格式)** [{"},{},{"},{} ]raceback (most recent call last): May 08 20:06:09 VM-0-2-ubuntu ragflow-s…...
基于FPGA的PID控制器verilog实现,包含simulink对比模型
目录 1.课题概述 2.系统测试效果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 根据PID控制器的原理,设计FPGA的总体架构。通常包括误差计算模块、比例运算模块、积分运算模块、微分运算模块、加法器模块以及控制信号输出模块等。同时通过simul…...
互联网大厂Java面试实录:Spring Boot与微服务架构在电商场景中的应用解析
💪🏻 1. Python基础专栏,基础知识一网打尽,9.9元买不了吃亏,买不了上当。 Python从入门到精通 😁 2. 毕业设计专栏,毕业季咱们不慌忙,几百款毕业设计等你选。 ❤️ 3. Python爬虫专栏…...
前端开发实战:用React Hooks优化你的组件性能
问题背景 在前端开发中,React组件的性能优化是一个常见挑战。尤其是当组件逻辑复杂或数据频繁更新时,性能问题尤为突出。本文将介绍如何利用React Hooks(如useMemo和useCallback)来优化组件性能。 解决方案 useMemo:用…...
Kotlin 内联函数深度解析:从源码到实践优化
一、内联函数核心概念 1. 什么是内联函数? 内联函数通过 inline 关键字修饰,其核心思想是:在编译时将函数体直接插入到调用处,而非进行传统的函数调用。这意味着: 消除了函数调用的栈帧创建、参数传递等开销。对 La…...
模拟太阳系(C#编写的maui跨平台项目源码)
源码下载地址:https://download.csdn.net/download/wgxds/90789056 本资源为用C#编写的maui跨平台项目源码,使用Visual Studio 2022开发环境,基于.net8.0框架,生成的程序为“模拟太阳系运行”。经测试,生成的程序可运行…...
python中的继承和多态
Python中的继承 继承中的一些基础的定义 继承是面向对象编程的三大特性之一,它允许一个类(子类)继承另一个类(父类)的属性和方法,从而实现代码的复用(继承的主要目的)和扩展。父类…...
【计算机视觉】3DDFA_V2中表情与姿态解耦及多任务平衡机制深度解析
3DDFA_V2中表情与姿态解耦及多任务平衡机制深度解析 1. 表情与姿态解耦的技术实现1.1 参数化建模基础1.2 解耦的核心机制1.2.1 基向量正交化设计1.2.2 网络架构设计1.2.3 损失函数设计 1.3 实现代码解析 2. 多任务联合学习的权重平衡2.1 任务定义与损失函数2.2 动态权重平衡策略…...
vue访问后端接口,实现用户注册
文章目录 一、后端接口文档二、前端代码请求响应工具调用后端API接口页面函数绑定单击事件,调用/api/user.js中的函数 三、参考视频 一、后端接口文档 二、前端代码 请求响应工具 /src/utils/request.js //定制请求的实例//导入axios npm install axios import …...
MySQL 从入门到精通(五):索引深度解析 —— 性能优化的核心武器
目录 一、索引概述:数据库的 “目录” 1.1 什么是索引? 1.2 索引的性能验证:用事实说话 实验环境准备 无索引查询耗时 有索引查询耗时 索引的 “空间换时间” 特性 二、索引的创建:三种核心方式 2.1 方式 1:C…...
湖北理元理律师事务所:债务优化如何实现还款与生活的平衡?
债务压力往往让债务人陷入“还款还是生存”的两难选择。湖北理元理律师事务所通过案例实践发现,科学规划的核心在于平衡法律义务与基本生活保障,而非单纯追求债务缩减。本文结合实务经验,解析债务优化的可行路径。 刚性需求优先:…...
Day21 奇异值分解(SVD)全面解析
一、奇异值分解概述 奇异值分解是线性代数中一个重要的矩阵分解方法,对于任何矩阵,无论是结构化数据转化成的“样本 * 特征”矩阵,还是天然以矩阵形式存在的图像数据,都能进行等价的奇异值分解(SVD)。 二…...
【vue】vuex实现组件间数据共享 vuex模块化编码 网络请求
目录 一、vuex实现组件间数据共享 二、 vuex模块化编码 三、网络请求 模块化命名空间小结: 总结不易~ 本章节对我有很大的收获, 希望对你也是!!! 本节素材已上传Gitee:yihaohhh/我爱Vue - Gitee.comhttps://gitee.…...
红黑树删除的实现与四种情况的证明
🧭 学习重点 删除节点的三种情况红黑树如何恢复性质四种修复情况完整可运行的 C 实现 一、红黑树删除的基础理解 红黑树删除比插入复杂得多,因为: 删除的是黑节点可能会破坏“从根到叶子黑节点数相等”的性质。删除红节点无需修复…...
FHE与后量子密码学
1. 引言 近年来,关于 后量子密码学(PQC, Post-Quantum Cryptography) 的讨论愈发热烈。这是因为安全专家担心,一旦有人成功研发出量子计算机,会发生什么可怕的事情。由于 Shor 算法的存在,量子计算机将能够…...
使用FastAPI和React以及MongoDB构建全栈Web应用04 MongoDB快速入门
一、NoSQL 概述 1.1 了解关系数据库的局限性 Before diving into NoSQL, it’s essential to understand the challenges posed by traditional Relational Database Management Systems (RDBMS). While RDBMS have been the cornerstone of data management for decades, th…...
C++:this指针
class date { public:void f(int i){} } 以上是我们定义的一个简单的类,这个类里面含有一个简单的成员函数,成员函数看似只有一个参数,实际上是两个参数,除了参数i以外,还有一个指向调用该函数的对象的指针——this指…...
如何在postman使用时间戳
1. 使用 Pre-request Script 动态转换 在发送请求前,将日期字符串转为时间戳并存储为环境变量/全局变量。 示例代码 // 将日期字符串(如 "2023-10-01")转为时间戳(毫秒) const dateString "2…...
OCP开闭原则
OCP,software entities(modules,classes,functions,etc.)should be openfor extension, but closed for modification. 软件实体(模块、类和方法等)应该对扩展开发,对修改关闭。 OCP特点 提高可扩展性:新功能通过添…...
计算机网络:什么是Mesh组网以及都有哪些设备支持Mesh组网?
Mesh组网技术详解与实现工具推荐 Mesh组网是一种通过多个节点路由器协同工作,形成覆盖全屋的无线网络的技术。它通过动态路径调整、无缝漫游和自愈能力,解决传统单一路由器覆盖不足的问题,尤其适合大户型、多层住宅或复杂户型环境。以下是Mesh组网的核心原理、实现方式及推…...
STM32f103 标准库 零基础学习之点灯
前提:你已经下好了标准外设库,如果没有可以去找找教程 ST官网上可以下载 目录 前提:你已经下好了标准外设库,如果没有可以去找找教程 ST官网上可以下载 点灯逻辑 1. 定义 GPIO 初始化结构体 2. 开启GPIOA的时钟…...
uniapp使用ui.request 请求流式输出
正文: 在现代Web开发中,实时数据流和长时间运行的请求变得越来越常见,尤其是在处理大量数据或进行实时通信时。在这种情况下,uniapp 提供的 ui.request 请求方法可以帮助我们轻松实现流式输出请求。本文将介绍如何使用 uni.reques…...
MATLAB安装常见问题及解决方案详解(含代码示例)
MATLAB作为科学计算和工程分析的核心工具,其安装过程可能因操作系统版本、硬件配置或网络环境等因素而出现各种问题。本文基于MATLAB官方文档和社区经验,系统总结了安装过程中常见的问题,并提供详细的解决方案和代码示例,帮助用户…...
【Java ee初阶】网络编程 UDP socket
网络编程 socket api 是传输层提供的api。 UDP 无连接,不可靠传输,面向数据报,全双工。 TCP 有链接,可靠传输,面向字节流,全双工。 UDP socket api 数据报 DatagrammSocket 代表了操作系统中的socket文…...
BeanPostProcessor和AOP
BeanPostProcessor Spring中有一个接口Oredr的getOrder()方法,这个方法返回值是一个int类型,Spring容器会根据这个方法的返回值 对容器的多个Processor对象从小到大排序,创建Bean时候依次执行他们的方法,也就是说getOrder()方法的…...
django的权限角色管理(RBAC)
在 Django 中,User、Group 和 Permission 是权限系统的核心组件。下面通过代码示例演示它们的 CRUD(创建、读取、更新、删除) 操作: 一、User 模型 CRUD from django.contrib.auth.models import User# 创建用户 user User.obje…...
vue vite 无法热更新问题
一、在vue页面引入组件CustomEmployeesDialog,修改组件CustomEmployeesDialog无法热更新 引入方式: import CustomEmployeesDialog from ../dialog/customEmployeesDialog.vue 目录结构: 最后发现是引入import时,路径大小写与目…...
IBM BAW(原BPM升级版)使用教程第八讲
续前篇! 一、流程开发功能模块使用逻辑和顺序 前面我们已经对 流程、用户界面、公开的自动化服务、服务、事件、团队、数据、性能、文件各个模块进行了详细讲解,现在统一进行全面统一讲解。 在 IBM Business Automation Workflow (BAW) 中,…...
202534 | KafKa简介+应用场景+集群搭建+快速入门
Apache Kafka 简介 一、什么是 Kafka? Apache Kafka 是一个高吞吐量、分布式、可扩展的流处理平台,用于构建实时数据管道和流应用程序。它最初由 LinkedIn 开发,并于 2011 年开源,目前由 Apache 软件基金会进行维护。 Kafka 具备…...
[思维模式-25]:《本质思考力》-6- 马克思主义哲学的五对基本哲学范畴,以及在计算机领域的体现
一、马克思主义哲学的五对基本哲学范畴, 马克思主义哲学的五对基本哲学范畴是内容与形式、现象与本质、原因与结果、必然性与偶然性、可能性与现实性,以下为具体分析: 内容与形式:组成元素 VS 组成结构 内容指构成事物内在要素的…...
6. 存储池配置与CephFS创建 ceph version 14.2.22
6. 存储池配置与CephFS创建 6.1 CRUSH规则管理6.2 纠删码配置6.3 为SSD和HDD创建专用CRUSH规则6.4 创建CephFS存储池6.5 验证存储池配置记录OSD盘符 所有节点都执行 7. 客户端挂载CephFS7.1 Ubuntu客户端配置7.2 使用内核驱动挂载7.3 设置开机自动挂载 说明:配置Cep…...
RocketMQ Kafka区别
架构 ZooKeeper:管理 Broker 注册、分区 Leader 选举及消费者组状态。Broker:存储 Partition数据,每个 Partition 为独立日志文件。Producer/Consumer:通过 ZooKeeper获取路由信息,实现消息分发与消费。 NameServer&am…...
linux和linux 、linux和windows实现文件复制笔记
前提:两设备得在同一局域网下,且启用了ssh 一、linux和linux实现文件复制 从 Ubuntu B 复制文件夹到 Ubuntu A 在 Ubuntu A 上打开终端,执行: scp -r userBubuntuB_IP:/home/userB/folder_to_copy /home/userA/destination/ 二、linux和…...
Flink 运维监控与指标采集实战
一、引言:实时任务为什么必须监控? 在实时任务中,任务失败、数据延迟、资源瓶颈往往并非由明显的代码异常引发,而是隐蔽地潜藏在: Kafka 积压无告警 Flink Checkpoint 卡顿却无人知晓 反压、TaskManager 内存 OOM 未实时感知 为了保障业务 SLA、高可用与可观测性,构建完…...
linux 开发小技巧之git增加指令别名
众所周知,git的指令执行时都得敲好几个字符才能补充上来,比如常用的git status,是不是要将全部的字符一个个地在键盘敲上来,有没有更懒惰点办法,可以将经常用到的git命令通过其他的别名的方式填充,比如刚刚…...
【八股消消乐】项目中如何优化JVM内存分配?
😊你好,我是小航,一个正在变秃、变强的文艺倾年。 🔔本专栏《八股消消乐》旨在记录个人所背的八股文,包括Java/Go开发、Vue开发、系统架构、大模型开发、机器学习、深度学习、力扣算法等相关知识点,期待与你…...