【问题标题】:Check string content of response before retrying with Polly在使用 Polly 重试之前检查响应的字符串内容
【发布时间】:2022-05-20 17:53:03
【问题描述】:

我正在使用一个非常不稳定的 API。有时我得到500 Server ErrorTimeout,有时我也得到500 Server Error,因为我给它输入了它无法处理SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM.

这两种情况都给了我HttpRequestException,但我可以查看来自服务器的回复消息并确定异常的原因。如果是超时错误,我应该再试一次。如果输入错误,我应该重新抛出异常,因为再多的重试都无法解决错误数据的问题。

我想对 Polly 执行的操作是在尝试重试之前检查响应消息。但是到目前为止我看到的所有示例都只包含异常类型。

到目前为止,我已经想出了这个:

        HttpResponseMessage response = null;
        String stringContent = null;
        Policy.Handle<FlakyApiException>()
             .WaitAndRetry(5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
              async (exception, timeSpan, context) =>
            {
                response = await client.PostAsync(requestUri, new StringContent(serialisedParameters, Encoding.UTF8, "application/json"));
                stringContent = await response.Content.ReadAsStringAsync();

                if (response.StatusCode == HttpStatusCode.InternalServerError && stringContent.Contains("Timeout"))
                {
                    throw new FlakyApiException(stringContent);
                }
            });

有没有更好的方法来做这种检查?

【问题讨论】:

    标签: c# polly retrypolicy


    【解决方案1】:

    通常,您可以配置 Polly 策略以响应执行结果(不仅仅是异常),例如使用谓词检查 HttpResponseMessage.StatusCode。示例here in the Polly readme

    然而,没有一种内置方法来配置单个 Polly 策略以额外响应响应消息的内容。这是因为(如您的示例所示)获取该内容需要第二次异步调用,这本身可能会引发网络错误。

    tl;dr 导致了如何表达(以简单的语法)单一策略的复杂性,该策略管理两个不同的异步步骤,每个步骤可能有不同的错误处理。之前相关讨论on Polly github:欢迎评论。

    因此,如果一个序列需要两个单独的异步调用,Polly 团队目前建议将其表达为两个单独的策略,类似于 the example in the end of this answer


    您问题中的特定示例可能不起作用,因为onRetryAsync 委托(抛出FlakyApiException)本身不受策略保护。策略只保护通过.Execute/ExecuteAsync(...) 执行的委托的执行。


    一种方法是使用两个策略,一个重试策略,重试所有典型的 http 异常和状态代码,包括 500;然后在其中一个 Polly FallbackPolicy 捕获代表 SqlDateTime overflow 的状态代码 500,并通过重新抛出一些可区分的异常 (CustomSqlDateOverflowException) 来排除该状态代码的重试。

            IAsyncPolicy<HttpResponseMessage> rejectSqlError = Policy<HttpResponseMessage>
                .HandleResult(r => r.StatusCode == HttpStatusCode.InternalServerError)
                .FallbackAsync(async (delegateOutcome, context, token) =>
                {
                    String stringContent = await delegateOutcome.Result.Content.ReadAsStringAsync(); // Could wrap this line in an additional policy as desired.
                    if (delegateOutcome.Result.StatusCode == HttpStatusCode.InternalServerError && stringContent.Contains("SqlDateTime overflow"))
                    {
                        throw new CustomSqlDateOverflowException(); // Replace 500 SqlDateTime overflow with something else.
                    }
                    else
                    {
                        return delegateOutcome.Result; // render all other 500s as they were
                    }
                }, async (delegateOutcome, context) => { /* log (if desired) that InternalServerError was checked for what kind */ });
    
            IAsyncPolicy<HttpResponseMessage> retryPolicy = Policy<HttpResponseMessage>
                .Handle<HttpRequestException>()
                .OrResult(r => r.StatusCode == HttpStatusCode.InternalServerError)
                .OrResult(r => /* condition for any other errors you want to handle */)
                .WaitAndRetry(5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                    async (exception, timeSpan, context) =>
                    {
                        /* log (if desired) retry being invoked */
                    });
    
            HttpResponseMessage response = await retryPolicy.WrapAsync(rejectSqlError)
                .ExecuteAsync(() => client.PostAsync(requestUri, new StringContent(serialisedParameters, Encoding.UTF8, "application/json"), cancellationToken));
    

    【讨论】:

    • 感谢您花时间写这篇文章。听起来比我希望的要复杂得多。让我把头绕过去试一试
    【解决方案2】:

    如果我正确理解您的问题,那么您只想在状态码为 500 且正文包含 Timeout 时重试。如果是这种情况,那么您可以像这样定义您的策略

    Policy<HttpResponseMessage>
        .HandleResult(response =>
            response.StatusCode == System.Net.HttpStatusCode.InternalServerError
            && response.Content.ReadAsStringAsync().GetAwaiter().GetResult().Contains("Timeout"))
        .WaitAndRetry(5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-12
      • 2017-10-22
      相关资源
      最近更新 更多