HttpClient 初始化与生命周期管理
HttpClient 旨在实例化一次,并在应用程序的整个生命周期内重复使用。
为实现复用,HttpClient类库默认使用连接池和请求管道,可以手动管理(连接池、配置管道、使用Polly); 结合IoC容器、工厂模式(提供了IHttpClientFactory类库)、复原库Polly,可以更加方便、完善的使用,这也是推荐的方法。
0、初始化与全局设置
csharp
//初始化:必须先执行一次
#!import ./ini.ipynb
1、手动管理:直接实例化-强烈不推荐
下面这种每次使用都实例化的用法是最常见、最不推荐的
:
因为HttpClient刚推出时不成熟及微软官方文档的示例代码是这种用法,再加上这种是最简单方便的使用方法,就造成很多人使用这种用法。
这种方法有如下缺点:
- 每次使用都实例化,造成性能开销大、容易内存泄露;
- 并发量大、请求频繁时:网络端口会被耗尽
Using包HttpClient,也只是在应用进程中释放了HttpClient实例,但http请求/响应是跨操作系统和网络的,而系统及网络问题在进程之上,不是进程所能处理的。
优点:
- 使用简单,好学易用;
- 并发量小且请求不频繁时,问题不大;
csharp
/*
每次请求都实例化:并发量大、请求频繁进会耗尽套接字端口
*/
{
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的重用,减少创建和销毁的开销
csharp
/*
静态类/属性
*/
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);
}
csharp
/*
单例实现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);
}
csharp
/*
单例实现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
- 可以灵活的进行统一配置
- 不同类别不同工具类,方便定制
- 业务直接封装成工具类方法,调用方便、快捷
缺点:
- 工具类比较多,需要手动维护
- 工具类方法比较多且和业务直接相关,需要手动维护
csharp
// 百度服务类
public sealed class BaiduService
{
private readonly HttpClient _httpClient;
public BaiduService()
{
//初始化httpClient
var 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);
}
csharp
// 本机服务类
// 百度服务类
public sealed class LocalService
{
private readonly HttpClient _httpClient;
public LocalService()
{
//初始化httpClient
var 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)请求
csharp
#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 times
OnRetry = 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 nullable
Console.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
csharp
/*
注意:
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 多服务类
csharp
// 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() 配合使用,也能达到默认客户端的配置效果。
csharp
//方式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实例。只执行一次。
csharp
//全局配置:所有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(""))。
csharp
//命名客户端
{
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.Empty
var defaultClient = factory.CreateClient();
var defaultResponse = await defaultClient.GetAsync("/api/hello/ping");
var defaultData = await defaultResponse.Content.ReadAsStringAsync();
Console.WriteLine(defaultData);
//name=clientA
var httpClient_a = factory.CreateClient(clientA);
var responseA = await httpClient_a.GetAsync("/api/hello/ping");
var dataA = await responseA.Content.ReadAsStringAsync();
dataA.Display();
//name=clientB
var 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,在其内部使用。
csharp
// 类型化客户端 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("========================================================================");
}
管道配置
csharp
//管道配置
//日志中间件(管道类)
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
包,进行通用日志配置!
csharp
//通用日志
{
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中的错误日志!");
}
配置默认日志
csharp
//配置默认日志(必须有常规日志及级别设置,否则不起使用)
{
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);
}
配置自定义日志
博客 可以参考
csharp
/* 添加自定义日志记录
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,进行常用功能使用。
csharp
/*
便捷应用: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 ,因此可以定义和应用任何类型的策略:可以指定要处理的内容和处理方式。
csharp
/*
传统方式配置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}");
}
}
应用多个策略
csharp
{
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);
}
}
动态选择策略
csharp
//实质是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);
}
从注册表中选择策略
csharp
{
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
csharp
/* 综合示例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());
}