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}");
}