Polly使用不同的请求主体进行重试请求。

3

我之前从未使用过Polly,也不确定这是否适合使用Polly。

我调用一个带有1000个DTO的列表的端点,在POST请求正文中。现在,该端点将对每个DTO执行一些验证,如果其中任何DTO未通过验证,则返回HTTP 400 Bad Request,并且响应还将包含所有未通过验证的DTO的id。因此,即使有一个DTO未通过验证,我也会收到HTTP 400响应。

现在我想知道是否可以优雅地处理已通过验证的其余DTO。

因此,每当我从API收到HTTP 400时,我希望更改请求有效负载以删除导致验证失败的DTO,并使用通过验证的其余DTO重试请求。

如何使用Polly实现这一点?

我正在使用.NET 5中的HttpClientFactory来使用类型化的HttpClient进行我的POST请求。


1
在这里,Polly 似乎不是合适的工具(即使你可能能够让它工作)。使用 Polly 处理瞬时故障等问题会更容易,然后编写一些简单的 C# 逻辑来完成其余部分。 - TheGeneral
1个回答

2
Polly的重试策略在触发时执行完全相同的操作。因此,默认情况下您无法更改请求。
但是,在实际重试发生之前,您可以在onRetryAsync委托中修改它。
为了演示这一点,我将使用WireMock.NET库来模拟您的下游系统。
因此,首先让我们创建一个Web服务器,该服务器侦听localhost上的40000端口,路由为/api
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调用放在重试中,这样我们就可以始终序列化最新的对象列表

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接