.Net HttpClient 处理错误与异常

HttpClient 处理错误与异常

根据客户端的创建方法不周,有不同的错误与异常处理方式。推荐类型化客户端、工厂和Polly库中统一处理!



初始化

#!import ./Ini.ipynb

常规方法

直接实例化客户端时,可以用Try Catch或者EnsureSuccessStatusCode方法简单处理, 这种是最不推荐的方式!

推荐使用后面介绍的 Pipeline 管道方式

  • try catch
/*
    try catch 处理异常
*/
try
{
    var requestUri = new Uri(webApiBaseUri, "/api/ErrorDemo/Error500");

    using(var client = new HttpClient())
    {
        //发送请求
        var response = await client.GetAsync(requestUri);
        if(response.IsSuccessStatusCode)
        {
            Console.WriteLine($"远程请求成功,响应码 {response.StatusCode}");
        }
        else
        {
            Console.WriteLine($"远程请求失败,响应码 {response.StatusCode}");
        }
    }
}
catch(Exception ex)
{
    Console.WriteLine($"远程调用异常:{ex.Message}");
}
finally
{
    //清理业务
}
  • Response的EnsureSuccessStatusCode方法
{ //EnsureSuccessStatusCode方法

    var requestUri = new Uri(webApiBaseUri, "/api/ErrorDemo/Error500");

    using(var client = new HttpClient())
    {
        //发送请求
        var response = await client.GetAsync(requestUri);

        //确保响应码正确,否则异常
        response.EnsureSuccessStatusCode();

        //输出响应码
        Console.WriteLine($"远程请求成功,响应码 {response.StatusCode}");
    }
}

静态或工具类中处理

静态类或工具类,可以在内部方法里使用 Try Catch 等常规方法进行异常处理。

注意:推荐使用后面介绍的 Pipeline 管道方式

  • 内部处理
//静态类中处理异常
public class HttpClientHelper
{
    public readonly static HttpClient StaticClient;

    static HttpClientHelper()
    {
        SocketsHttpHandler handler = new SocketsHttpHandler()
        {
            PooledConnectionLifetime = TimeSpan.FromSeconds(30),
        };

        StaticClient = new HttpClient(handler)
        {
            //统一设置: 基础Uri
            BaseAddress = new Uri(WebApiConfigManager.GetWebApiConfig().BaseUrl),
        };

        //统一设置:请求头等
        StaticClient.DefaultRequestHeaders.Add("x-custom-time","12ms");

        //统一错误处理:可以在Pipline中统一设置,后面有单独章节
    } 

    public static async Task<HttpResponseMessage> GetAsync(string url)
    {
        //异常处理
        try
        {
            return await StaticClient.GetAsync(url);
        }
        catch(Exception ex)
        {
            Console.WriteLine($"远程调用发生异常:{ex.Message}");
            return await Task.FromResult(new HttpResponseMessage(HttpStatusCode.ExpectationFailed));
        }
        finally
        {
            //清理业务
        }
    }

    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)
    {
        //异常处理
        try
        {
            return await StaticClient.PostAsync(url, content);
        }
        catch(Exception ex)
        {
            Console.WriteLine($"远程调用发生异常:{ex.Message}");
            return await Task.FromResult(new HttpResponseMessage(HttpStatusCode.ExpectationFailed));
        }
        finally
        {
            //清理业务
        }
    }
}

{   //正常请求
    var response = await HttpClientHelper.GetAsync("/api/Config/GetApiConfig");
    var content = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"正常请求结果:{content}");

    //异常请求
    var response2 = await HttpClientHelper.GetAsync("/api/ErrorDemo/Error500");
    Console.WriteLine($"异常请求结果:{response2}");
}
  • 添加异常Pipeline管道(下面专项介绍)

类型化客户端中处理

类型化客户端有三种方法:一是类内部处理;二是Pipeline,统一处理;三是结合IoC/工厂和Polly库,初始设置时统一处理。

推荐第二、第三种,后面有专项介绍。

  • 内部处理,类似静态或工具类
// 类型化客户端A HttpClient
public class HttpClientServiceA
{
    public HttpClient Client { get; }
    public HttpClientServiceA(HttpClient client)
    {
        Client = client;
        Console.WriteLine("HttpClientServiceA => 构造函数执行一次");
    }

    //常规方法:异常可在方法内处理
    public async Task<string> GetAsync()
    {
        var response = await Client.GetAsync("/api/ErrorDemo/Error500");
        var content = await response.Content.ReadAsStringAsync();
        return content;
    }
}

// 使用类型化客户端
{
    var services = new ServiceCollection();
    services
        .AddHttpClient<HttpClientServiceA>()
        .ConfigureHttpClient(client=>
        {
            client.BaseAddress = new Uri(webApiBaseUrl);
        });

    var builder = services.BuildServiceProvider();
    var serverA = builder.GetRequiredService<HttpClientServiceA>();

    var dataA = await serverA.GetAsync();
    Console.WriteLine(dataA);
}
  • 与工厂和Polly库统合,统一处理(下面专项介绍)
  • 添加异常Pipeline管道(下面专项介绍)

在管道中,添加异常中间件

不管那种方式创建的客户端,都可以配置Pipeline,专门添加异常管道中间件进行异常统一处理,也很推荐这种方式。

  • 准备异常处理中间件
//异常中间件(管道)类
public class ExceptionDelegatingHandler : DelegatingHandler
{
    protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Console.WriteLine("ExceptionDelegatingHandler -> Send -> Before");

        HttpResponseMessage response;
        try 
        {
            response = base.Send(request, cancellationToken);
            response.EnsureSuccessStatusCode();
        }
        catch(Exception ex)
        {
            Console.WriteLine($"异常管道中间件,监控到异常:{ex.Message}");

            response = new HttpResponseMessage(HttpStatusCode.ExpectationFailed);
        }
        finally
        {
            //清理业务
        }
        
        Console.WriteLine("ExceptionDelegatingHandler -> Send -> After");

        return response;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Console.WriteLine("ExceptionDelegatingHandler -> SendAsync -> Before");

        HttpResponseMessage response;
        try 
        {
            response = response = await base.SendAsync(request, cancellationToken);
            response.EnsureSuccessStatusCode();
        }
        catch(Exception ex)
        {
            Console.WriteLine($"异常管道中间件,监控到异常:{ex.Message}");

            response = new HttpResponseMessage(HttpStatusCode.ExpectationFailed);
        }
        finally
        {
            //清理业务
        }

        Console.WriteLine("ExceptionDelegatingHandler -> SendAsync -> After");

        return response;
    }
}
  • 直接创建客户端时,加入异常管道
{

    //构建管道(加入异常中间件)
    var handler = new ExceptionDelegatingHandler()
    {
        //相当于下一个中间件(管道)
        //最后中间件必须是SocketsHttpHandler
        InnerHandler = new SocketsHttpHandler() 
        {
            AllowAutoRedirect = true
        }
    };

    //构造中传入管道对象
    using(var client = new HttpClient(handler){BaseAddress = new Uri(webApiBaseUrl)})
    {
        //发送请求
        var response = await client.GetAsync("/api/ErrorDemo/Error500");

        if(response.IsSuccessStatusCode)
        {
            Console.WriteLine($"远程请求成功,响应码 {response.StatusCode}");
        }
        else
        {
            Console.WriteLine($"远程请求失败,响应码 {response.StatusCode}");
        }
    }
}
  • 静态或工具类,加入异常管道
//静态类中处理异常
public class HttpClientHelper2
{
    public readonly static HttpClient StaticClient;

    static HttpClientHelper2()
    {
        //构建管道(加入异常中间件)
        var handler = new ExceptionDelegatingHandler()
        {
            //相当于下一个中间件(管道)
            //最后中间件必须是SocketsHttpHandler
            InnerHandler = new SocketsHttpHandler() 
            {
                AllowAutoRedirect = true,
                PooledConnectionLifetime = TimeSpan.FromSeconds(30),
            }
        };

        StaticClient = new HttpClient(handler)
        {
            //统一设置: 基础Uri
            BaseAddress = new Uri(WebApiConfigManager.GetWebApiConfig().BaseUrl),
        };

        //统一设置:请求头等
        StaticClient.DefaultRequestHeaders.Add("x-custom-time","12ms");

        //统一错误处理:可以在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);

        //无异常处理

        return await response.Content.ReadAsStringAsync();
    }

    public static async Task<HttpResponseMessage> PostAsync(string url, HttpContent content)
    {
        //无异常处理
        return await StaticClient.PostAsync(url,content);
    }
}

{   //正常请求
    var response = await HttpClientHelper2.GetAsync("/api/Config/GetApiConfig");
    var content = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"正常请求结果:{content}");

    //异常请求
    var response2 = await HttpClientHelper2.GetAsync("/api/ErrorDemo/Error500");
    Console.WriteLine($"异常请求结果:{response2}");
}
  • 类型化客户端,加入异常管道
// 类型化客户端A HttpClient
public class HttpClientServiceC
{
    public HttpClient Client { get; }
    public HttpClientServiceC(HttpClient client)
    {
        Client = client;
        Console.WriteLine("HttpClientServiceC => 构造函数执行一次");
    }

    //常规方法:异常可在方法内处理
    public async Task<string> GetAsync()
    {
        var response = await Client.GetAsync("/api/ErrorDemo/Error500");
        var content = await response.Content.ReadAsStringAsync();
        return content;
    }
}

// 使用类型化客户端
{
    var services = new ServiceCollection();
    services
        //1、注入异常管道到服务
        .AddTransient<ExceptionDelegatingHandler>()
        .AddHttpClient<HttpClientServiceC>()
        .ConfigureHttpClient(client=>
        {
            client.BaseAddress = new Uri(webApiBaseUrl);
        })
        //2、添加异常管道至客户端
        .AddHttpMessageHandler<ExceptionDelegatingHandler>();

    var builder = services.BuildServiceProvider();
    var serverC = builder.GetRequiredService<HttpClientServiceC>();

    var dataC = await serverC.GetAsync();
    Console.WriteLine($"响应数据为:{dataC}");
}
  • 工厂,加入异常管道
//使用
{
    var services = new ServiceCollection();

    //基础配置
    services
        //1、注入异常管道到服务
        .AddTransient<ExceptionDelegatingHandler>()
        //全局配置
        .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<HttpClientServiceC>()

        //2、添加异常管道至客户端
        .AddHttpMessageHandler<ExceptionDelegatingHandler>()

        //配置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 responseA = await clientA.GetAsync("/api/ErrorDemo/Error500");
    Console.WriteLine($"响应码:{responseA.StatusCode}");
}

工厂 + Polly库处理

Polly库提供了多种多样的超时、异常等功能,非常推荐使用(可以配合IoC/工厂)。

下面是简单例子:

//polly策略
var policy = Policy
    .Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(message => message.StatusCode != System.Net.HttpStatusCode.OK)
    //后备策略,代替异常
    .FallbackAsync(async fallbackAction =>
    {
       Console.WriteLine("运程调用发性异常,Polly进行了回退!");

       return await Task.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError));
    });

//工厂中使用 Polly异常回退策略
{
   var services = new ServiceCollection();

   //默认命名客户端
   services
      .AddHttpClient<HttpClient>(string.Empty)
      .ConfigureHttpClient(client => 
      {
         client.BaseAddress = new Uri(webApiBaseUrl);
         client.Timeout = TimeSpan.FromSeconds(10);
      })

      //Polly策略配置
      .AddPolicyHandler(policy);

    
    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 responseA = await clientA.GetAsync("/api/ErrorDemo/Error500");
    Console.WriteLine($"异常请求,响应码:{responseA.StatusCode}");
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bicijinlian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值