Nie*_*nch 1 c# timeout dotnet-httpclient polly retry-logic
我试图让 Polly 在 3 秒后超时以及返回某些 http 代码时再次尝试。但是,直到 100 秒后 HttpClient 超时后才会超时。
这是我的代码:
private static Polly.Wrap.AsyncPolicyWrap<HttpResponseMessage> GetPolicy()
{
var timeoutPolicy = Policy.TimeoutAsync(3, Polly.Timeout.TimeoutStrategy.Optimistic);
var retryPolicy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r =>
r.StatusCode == HttpStatusCode.TooManyRequests ||
r.StatusCode == HttpStatusCode.ServiceUnavailable ||
r.StatusCode == HttpStatusCode.Forbidden)
.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(3));
var policy = retryPolicy.WrapAsync(timeoutPolicy);
return policy;
}
Run Code Online (Sandbox Code Playgroud)
更新
根据要求,这是我使用该策略的代码。
var pollyResponse = await GetPolicy().ExecuteAndCaptureAsync(() =>
httpClient.SendAsync(GetMessage(HttpMethod.Delete, endpoint))
);
Run Code Online (Sandbox Code Playgroud)
以及生成 HttpRequestMessage 的辅助方法:
private HttpRequestMessage GetMessage<T>(HttpMethod method, string endpoint, T content)
{
var message = new HttpRequestMessage
{
Method = method,
RequestUri = new Uri(endpoint),
Headers = {
{ "MyCustomHeader", _value },
{ HttpRequestHeader.Accept.ToString(), "application/json" }
}
};
if (content != null)
{
var contentAsString = JsonSerializer.Serialize(content);
message.Content = new StringContent(contentAsString);
}
return message;
}
Run Code Online (Sandbox Code Playgroud)
首先,让我与您分享您的修订版GetPolicy:
private static IAsyncPolicy<HttpResponseMessage> GetStrategy()
{
var timeoutPolicy = Policy
.TimeoutAsync<HttpResponseMessage>(3, TimeoutStrategy.Optimistic,
onTimeoutAsync: (_, __, ___, ____) =>
{
Console.WriteLine("Timeout has occurred");
return Task.CompletedTask;
});
var retryPolicy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutRejectedException>()
.OrResult<HttpResponseMessage>(r =>
r.StatusCode == (HttpStatusCode)429 ||
r.StatusCode == HttpStatusCode.ServiceUnavailable ||
r.StatusCode == HttpStatusCode.Forbidden)
.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(3),
onRetryAsync: (_, __, ___) =>
{
Console.WriteLine("Retry will fire soon");
return Task.CompletedTask;
});
return Policy.WrapAsync(retryPolicy, timeoutPolicy);
}
Run Code Online (Sandbox Code Playgroud)
PolicyWrap只是一个实现细节
AsyncPolicy<T>如果您不想使用接口,也可以使用抽象类作为返回类型 ( IAsyncPolicy<T>)onTimeoutAsync、onRetryAsync),以便能够查看何时触发哪个策略Or<TimeoutRejectedException>()在 上添加了一个构建器函数调用,retryPolicy以确保在超时时触发重试
retryPolicy.WrapAsync为PolicyWrap,因为升级链更加明确
timeoutPolicy(.TimeoutAsync < HttpResponseMessage > ) 以与重试策略保持一致(它们都包装了一个可能返回 a 的委托Task<HttpResponseMessage>)为了能够测试我们的弹性策略(注意命名),我创建了以下辅助方法:
private static HttpClient client = new HttpClient();
public static async Task<HttpResponseMessage> CallOverloadedAPI(int responseDelay = 5000, int responseCode = 200)
{
return await client.GetAsync($"http://httpstat.us/{responseCode}?sleep={responseDelay}");
}
Run Code Online (Sandbox Code Playgroud)
现在,我们调用该网站:
public static async Task Main()
{
HttpResponseMessage response;
try
{
response = await GetStrategy().ExecuteAsync(async () => await CallOverloadedAPI());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Environment.Exit(-1);
}
Console.WriteLine("Finished");
}
Run Code Online (Sandbox Code Playgroud)
输出:
Finished
Run Code Online (Sandbox Code Playgroud)
等等,什么??? 问题是没有任何政策被触发。
为什么? 因为 5 秒后我们收到了 200 的回复。
但是,我们已经设置了超时,对吗? 是和不是。:) 尽管我们已经定义了超时策略,但我们还没有真正将其连接到 HttpClient
那么,我该如何连接呢?
嗯,通过CancellationToken
因此,在超时策略的情况下,如果 aCancellationToken正在使用,那么它可以调用其Cancel方法向 HttpClient 指示超时事实。并且 HttpClient 将取消挂起的请求。
请注意,因为我们使用 TimeoutPolicy,所以异常将是TimeoutRejectedException,而不是OperationCanceledException。
所以,让我们修改我们的代码以接受CancellationToken
public static async Task<HttpResponseMessage> CallOverloadedAPI(int responseDelay = 5000, int responseCode = 200, CancellationToken token = default)
{
return await client.GetAsync($"http://httpstat.us/{responseCode}?sleep={responseDelay}", token);
}
Run Code Online (Sandbox Code Playgroud)
我们还必须调整使用方面:
public static async Task Main()
{
HttpResponseMessage response;
try
{
response = await GetStrategy().ExecuteAsync(async (ct) => await CallOverloadedAPI(token: ct), CancellationToken.None);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Environment.Exit(-1);
}
Console.WriteLine("Finished");
}
Run Code Online (Sandbox Code Playgroud)
现在的输出将如下所示:
private static IAsyncPolicy<HttpResponseMessage> GetStrategy()
{
var timeoutPolicy = Policy
.TimeoutAsync<HttpResponseMessage>(3, TimeoutStrategy.Optimistic,
onTimeoutAsync: (_, __, ___, ____) =>
{
Console.WriteLine("Timeout has occurred");
return Task.CompletedTask;
});
var retryPolicy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutRejectedException>()
.OrResult<HttpResponseMessage>(r =>
r.StatusCode == (HttpStatusCode)429 ||
r.StatusCode == HttpStatusCode.ServiceUnavailable ||
r.StatusCode == HttpStatusCode.Forbidden)
.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(3),
onRetryAsync: (_, __, ___) =>
{
Console.WriteLine("Retry will fire soon");
return Task.CompletedTask;
});
return Policy.WrapAsync(retryPolicy, timeoutPolicy);
}
Run Code Online (Sandbox Code Playgroud)
最后一行是Message的TimeoutRejectedException。
请注意,如果我们Or<TimeoutRejectedException>()从retryPolicy构建器中删除调用,则输出将如下:
private static HttpClient client = new HttpClient();
public static async Task<HttpResponseMessage> CallOverloadedAPI(int responseDelay = 5000, int responseCode = 200)
{
return await client.GetAsync($"http://httpstat.us/{responseCode}?sleep={responseDelay}");
}
Run Code Online (Sandbox Code Playgroud)
因此,现在将触发重试。不会有升级。
为了完整起见,以下是整个源代码:
public static async Task Main()
{
HttpResponseMessage response;
try
{
response = await GetStrategy().ExecuteAsync(async (ct) => await CallOverloadedAPI(token: ct), CancellationToken.None);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Environment.Exit(-1);
}
Console.WriteLine("Finished");
}
private static AsyncPolicy<HttpResponseMessage> GetStrategy()
{
var timeoutPolicy = Policy
.TimeoutAsync<HttpResponseMessage>(3, TimeoutStrategy.Optimistic,
onTimeoutAsync: (_, __, ___, ____) =>
{
Console.WriteLine("Timeout has occurred");
return Task.CompletedTask;
});
var retryPolicy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutRejectedException>()
.OrResult<HttpResponseMessage>(r =>
r.StatusCode == (HttpStatusCode)429 ||
r.StatusCode == HttpStatusCode.ServiceUnavailable ||
r.StatusCode == HttpStatusCode.Forbidden)
.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(3),
onRetryAsync: (_, __, ___) =>
{
Console.WriteLine("Retry will fire soon");
return Task.CompletedTask;
});
return Policy.WrapAsync(retryPolicy, timeoutPolicy);
}
private static HttpClient client = new HttpClient();
public static async Task<HttpResponseMessage> CallOverloadedAPI(int responseDelay = 5000, int responseCode = 200, CancellationToken token = default)
{
return await client.GetAsync($"http://httpstat.us/{responseCode}?sleep={responseDelay}", token);
}
Run Code Online (Sandbox Code Playgroud)