我以前从未使用过Polly,我不确定这对Polly来说是不是一个好的场景。
我在POST
体中调用具有1000个DTO
列表的端点。现在,端点将对每个DTO
执行一些验证,如果这些dto中的任何一个验证失败,则返回一个HTTP 400 Bad Request,并且响应还将包含所有验证失败的DTO
的id
。因此,即使一个DTO
验证失败,我也会得到一个HTTP 400响应。
现在我想知道我是否可以优雅地处理通过验证的剩余dto。
因此,每当我从API获得HTTP 400时,我想更改请求有效负载以删除导致验证失败的DTO
s,并使用剩余的DTO
s重试请求。
我怎样才能让Polly做到这一点呢?
我使用类型的HttpClient
使用HttpClientFactory
在。net 5使我的POST
请求。
Polly的重试策略执行完全相同的操作无论何时触发。因此,默认情况下,您不能更改请求。
但是您可以在onRetryAsync
委托中修改它,该委托在实际重试发生之前被触发。
为了演示这一点,我将使用WireMock。. NET库来模拟下游系统。
因此,首先让我们创建一个web服务器,它在/api
路由上监听localhost
上的40000
端口:
protected const string route = "/api";
protected const int port = 40_000;
protected static readonly WireMockServer server = WireMockServer.Start(port);
protected static readonly IRequestBuilder endpointSetup = Request.Create().WithPath(route).UsingPost();
然后设置一个场景来模拟第一个请求以400失败,然后第二个请求以200成功。
protected const string scenario = "polly-retry-test";
server.Reset();
server
.Given(endpointSetup)
.InScenario(scenario)
.WillSetStateTo(1)
.WithTitle("Failed Request")
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.BadRequest));
server
.Given(endpointSetup)
.InScenario(scenario)
.WhenStateIs(1)
.WithTitle("Succeeded Request")
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK));
让我们测试一下
protected static readonly HttpClient client = new HttpClient();
var result = await client.PostAsync($"http://localhost:{port}{route}", new StringContent(""));
Console.WriteLine(result.StatusCode);
result = await client.PostAsync($"http://localhost:{port}{route}", new StringContent(""));
Console.WriteLine(result.StatusCode);
的输出将如下所示:
BadRequest
OK
好了,现在我们有了一个下游模拟器,现在是时候关注重试策略了。为了简单起见,我将序列化一个List<int>
集合,并将其作为有效负载发布。
我设置重试每当它收到一个400,然后在它的onRetryAsync
委托我检查响应并删除不需要的整数。
AsyncRetryPolicy<HttpResponseMessage> retryInCaseOfPartialSuccess = Policy
.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.BadRequest)
.RetryAsync(1, onRetryAsync: (dr, _, __) => {
//TODO: Process response from: `dr.Result.Content`
//TODO: Replace the removal logic to appropriate one
dtoIds.RemoveAt(0);
return Task.CompletedTask;
});
让我们用修饰的重试策略调用下游API:
await retryInCaseOfPartialSuccess.ExecuteAsync(async (_) => {
var payload = JsonSerializer.Serialize(dtoIds);
Console.WriteLine(payload); //Only for debugging purposes
return await client.PostAsync($"http://localhost:{port}{route}", new StringContent(payload));
}, CancellationToken.None);
让我们把这些放在一起:
protected const string scenario = "polly-retry-test";
protected const string route = "/api";
protected const int port = 40_000;
protected static readonly WireMockServer server = WireMockServer.Start(port);
protected static readonly IRequestBuilder endpointSetup = Request.Create().WithPath(route).UsingPost();
protected static readonly HttpClient client = new HttpClient();
private static async Task Main()
{
server.Reset();
server
.Given(endpointSetup)
.InScenario(scenario)
.WillSetStateTo(1)
.WithTitle("Failed Request")
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.BadRequest));
server
.Given(endpointSetup)
.InScenario(scenario)
.WhenStateIs(1)
.WithTitle("Succeeded Request")
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK));
//var result = await client.PostAsync($"http://localhost:{port}{route}", new StringContent(""));
//Console.WriteLine(result.StatusCode);
//result = await client.PostAsync($"http://localhost:{port}{route}", new StringContent(""));
//Console.WriteLine(result.StatusCode);
await IssueRequestAgainstDownstream(new List<int> { 1, 2 });
}
private static async Task IssueRequestAgainstDownstream(List<int> dtoIds)
{
AsyncRetryPolicy<HttpResponseMessage> retryInCaseOfPartialSuccess = Policy
.HandleResult<HttpResponseMessage>(response => response.StatusCode == HttpStatusCode.BadRequest)
.RetryAsync(1, onRetryAsync: (dr, _, __) => {
//TODO: Process response from: `dr.Result.Content`
//TODO: Replace the removal logic to appropriate one
dtoIds.RemoveAt(0);
return Task.CompletedTask;
});
await retryInCaseOfPartialSuccess.ExecuteAsync(async (_) => {
var payload = JsonSerializer.Serialize(dtoIds);
Console.WriteLine(payload); //Only for debugging purposes
return await client.PostAsync($"http://localhost:{port}{route}", new StringContent(payload));
}, CancellationToken.None);
}
那么,我们做了什么?
- 创建下游模拟以模拟400和200个后续响应
- 创建一个重试策略,如果收到400 ,可以修改请求的有效负载。
- 将序列化逻辑和http调用放在重试中,这样我们就可以始终序列化最近的对象列表