.Net HttpClient 管理客户端(初始化与生命周期管理)

news2025/5/14 16:26:45

HttpClient 初始化与生命周期管理

HttpClient 旨在实例化一次,并在应用程序的整个生命周期内重复使用。

为实现复用,HttpClient类库默认使用连接池和请求管道,可以手动管理(连接池、配置管道、使用Polly); 结合IoC容器、工厂模式(提供了IHttpClientFactory类库)、复原库Polly,可以更加方便、完善的使用,这也是推荐的方法。

0、初始化与全局设置

//初始化:必须先执行一次
#!import ./ini.ipynb

1、手动管理:直接实例化-强烈不推荐

下面这种每次使用都实例化的用法是最常见、最不推荐的

因为HttpClient刚推出时不成熟及微软官方文档的示例代码是这种用法,再加上这种是最简单方便的使用方法,就造成很多人使用这种用法。
这种方法有如下缺点:

  1. 每次使用都实例化,造成性能开销大、容易内存泄露;
  2. 并发量大、请求频繁时:网络端口会被耗尽 Using包HttpClient,也只是在应用进程中释放了HttpClient实例,但http请求/响应是跨操作系统和网络的,而系统及网络问题在进程之上,不是进程所能处理的。

优点:

  1. 使用简单,好学易用;
  2. 并发量小且请求不频繁时,问题不大;
/*
    每次请求都实例化:并发量大、请求频繁进会耗尽套接字端口
*/
{ 
    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的重用,不推荐的

缺点:

  1. 不够灵活、优雅:特别是有多个系列的请求时;

优点:

  1. 复用 HttpClient
  2. 实现了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、手动管理:多工具类(每类请求对应一种工具类或单例类)

把不同类别的请求分成不同的工具类,业务类直接封装成工具类的方法。类似类型化的客户端。 简单使用的话,比较推荐

优点:

  1. 复用HttpClient
  2. 可以灵活的进行统一配置
  3. 不同类别不同工具类,方便定制
  4. 业务直接封装成工具类方法,调用方便、快捷

缺点:

  1. 工具类比较多,需要手动维护
  2. 工具类方法比较多且和业务直接相关,需要手动维护
// 百度服务类
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);
}
// 本机服务类
// 百度服务类
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)请求

#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

/*
    注意:
        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.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,在其内部使用。

// 类型化客户端 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.LoggingMicrosoft.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 官方参考
使用步骤:

  1. 引用 Polly v8 和 Microsoft.Extensions.Http.Polly 包
  2. 配置命名客户端
  3. 使用 AddTransientHttpErrorPolicy 快捷方法,配置策略
  4. 使用其它方式配置,并且可以使用多策略、注册策略、上下文等功能

基础应用

使用快捷方法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());

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2375518.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL OCP试题解析(3)

试题如图所示&#xff1a; 一、解析 正确选项&#xff1a;D&#xff09;The backup can be impacted when DDL operations run during the backup&#xff08;备份期间运行的 DDL 操作可能影响备份&#xff09; 1. 关键知识点解析&#xff1a; 题目中的命令 mysqlbackup 使用了…

SpringCloud之Gateway基础认识-服务网关

0、Gateway基本知识 Gateway 是在 Spring 生态系统之上构建的 API 网关服务&#xff0c;基于 Spring &#xff0c;Spring Boot 和 Project Reactor 等技术。 Gateway 旨在提供一种简单而有效的方式来对 API 进行路由&#xff0c;以及提供一些强大的过滤器功能&#xff0c;例如…

STM32-DMA数据转运(8)

目录 一、简介 二、存储器映像 三、DMA框图​编辑 四、DMA基本结构 五、两个数据转运的实例 一、简介 直接存储器存取简称DMA&#xff08;Direct Memory Access&#xff09;&#xff0c;它是一个数据转运小助手&#xff0c;主要用来协助CPU&#xff0c;完成数据转运的工作…

电机控制储备知识学习(一) 电机驱动的本质分析以及与磁相关的使用场景

目录 电机控制储备知识学习&#xff08;一&#xff09;一、电机驱动的本质分析以及与磁相关的使用场景1&#xff09;电机为什么能够旋转2&#xff09;电磁原理的学习重要性 二、电磁学理论知识1&#xff09;磁场基础知识2&#xff09;反电动势的公式推导 附学习参考网址欢迎大家…

使用 React 实现语音识别并转换功能

在现代 Web 开发中&#xff0c;语音识别技术的应用越来越广泛。它为用户提供了更加便捷、自然的交互方式&#xff0c;例如语音输入、语音指令等。本文将介绍如何使用 React 实现一个简单的语音识别并转换的功能。 功能概述 我们要实现的功能是一个语音识别测试页面&#xff0…

[Git]ssh下用Tortoisegit每次提交都要输密码

问题描述 ssh模式下&#xff0c;用小乌龟提交代码&#xff0c;即使在git服务端存储了公钥&#xff0c;仍然要每次输入密码。 原因分析 小乌龟需要额外配置自己的密钥&#xff0c;才能免除每次输密码。 解决方案 1.配置好ssh密钥 具体方法参考我前一篇文章&#xff1a; […

如何查看项目是否支持最新 Android 16K Page Size 一文汇总

前几天刚聊过 《Google 开始正式强制 Android 适配 16 K Page Size》 之后&#xff0c;被问到最多的问题是「怎么查看项目是否支持 16K Page Size」 &#xff1f;其实有很多直接的方式&#xff0c;但是最难的是当你的项目有很多依赖时&#xff0c;怎么知道这个「不支持的动态库…

ESP32C3连接wifi

文章目录 &#x1f527; 一、ESP32-C3 连接 Wi-Fi 的基本原理&#xff08;STA 模式&#xff09;✅ 二、完整代码 注释讲解&#xff08;适配 ESP32-C3&#xff09;&#x1f4cc; 三、几个关键点解释&#x1f51a; 四、小结 &#x1f527; 一、ESP32-C3 连接 Wi-Fi 的基本原理&a…

机器学习中分类模型的常用评价指标

评价指标是针对模型性能优劣的一个定量指标。 一种评价指标只能反映模型一部分性能&#xff0c;如果选择的评价指标不合理&#xff0c;那么可能会得出错误的结论&#xff0c;故而应该针对具体的数据、模型选取不同的的评价指标。 本文将详细介绍机器学习分类任务的常用评价指…

MySQL的Docker版本,部署在ubantu系统

前言 MySQL的Docker版本&#xff0c;部署在ubantu系统&#xff0c;出现问题&#xff1a; 1.执行一个SQL&#xff0c;只有错误编码&#xff0c;没有错误提示信息&#xff0c;主要影响排查SQL运行问题&#xff1b; 2.这个问题&#xff0c;并不影响实际的MySQL运行&#xff0c;如…

Mac QT水平布局和垂直布局

首先上代码 #include "mainwindow.h" #include "ui_mainwindow.h" #include <QPushButton> #include<QVBoxLayout>//垂直布局 #include<QHBoxLayout>//水平布局头文件 MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), …

回答 | 图形数据库neo4j社区版可以应用小型企业嘛?

刚在知乎上看到了一个提问&#xff0c;挺有意思&#xff0c;于是乎&#xff0c;贴到这里再简聊一二。 转自知乎提问 当然可以&#xff0c;不过成本问题不容小觑。另外还有性能上的考量。 就在最近&#xff0c;米国国家航空航天局——NASA因为人力成本问题&#xff0c;摒弃了使…

Linux操作系统从入门到实战(二)手把手教你安装VMware17pro与CentOS 9 stream,实现Vim配置,并配置C++环境

Linux操作系统从入门到实战&#xff08;二&#xff09;手把手教你安装VMware17pro与CentOS 9.0 stream&#xff0c;实现Vim配置&#xff0c;并编译C文件 前言一、安装VMware17pro二、安装CentOS9.02.1 为什么选择CentOS9&#xff0c;与CentOS7对比2.1 官网下载CentOS9.02.2 国内…

软考架构师考试-UML图总结

考点 选择题 2-4分 案例分析0~1题和面向对象结合考察&#xff0c;前几年固定一题。近3次考试没有出现。但还是有可能考。 UML图概述 1.用例图&#xff1a;描述系统功能需求和用户&#xff08;参与者&#xff09;与系统之间的交互关系&#xff0c;聚焦于“做什么”。 2.类图&…

论文学习_Trex: Learning Execution Semantics from Micro-Traces for Binary Similarity

摘要&#xff1a;检测语义相似的函数在漏洞发现、恶意软件分析及取证等安全领域至关重要&#xff0c;但该任务面临实现差异大、跨架构、多编译优化及混淆等挑战。现有方法多依赖语法特征&#xff0c;难以捕捉函数的执行语义。对此&#xff0c;TREX 提出了一种基于迁移学习的框架…

在VirtualBox中安装虚拟机后不能全屏显示的问题及解决办法

在VirtualBox中安装Windows或Linux虚拟机后&#xff0c;将遇到启动虚拟机后&#xff0c;只能在屏幕中的一块区域里显示虚拟机桌面&#xff0c;却不能全屏显示的问题。要解决此问题&#xff0c;需要在虚拟机中安装与VirtualBox版本相对应的VBox GuestAdditons软件。 这里…

element-ui分页的使用及修改样式

1.安装 npm install element-ui -S 2.在main.js中引入,这里是全部引入&#xff0c;也可以按需引入 import ElementUI from element-ui import element-ui/lib/theme-chalk/index.css Vue.use(ElementUI) 3.使用 layout"prev, pager, next, jumper" &#xff1a;jumpe…

从数据中台到数据飞轮:数字化转型的演进之路

从数据中台到数据飞轮&#xff1a;数字化转型的演进之路 数据中台 数据中台是企业为整合内部和外部数据资源而构建的中介层&#xff0c;实现数据的统一管理、共享和高效利用&#xff0c;目标是打破信息孤岛&#xff0c;提高数据使用效率&#xff0c;支持业务决策和创新 实施成本…

2025年5月-信息系统项目管理师高级-软考高项一般计算题

决策树和期望货币值 加权算法 自制和外购分析 沟通渠道 三点估算PERT 当其他条件一样时&#xff0c;npv越大越好

zst-2001 上午题-历年真题 算法(5个内容)

回溯 算法 - 第1题 找合适的位置&#xff0c;如果没有位置就按B回家 d 分治 算法 - 第2题 b 算法 - 第3题 a 算法 - 第4题 划分一般就是分治 a 算法 - 第5题 分治 a 0-1背包 算法 - 第6题 c 算法 - 第7题 最小的为c 3100 c 算法 - 第8题 …