Перед повторной попыткой с помощью Polly проверьте содержимое строки ответа

Я работаю с очень нестабильным API. Иногда я получаю 500 Server Error с Timeout, в другой раз я также получаю 500 Server Error, потому что я дал ему ввод, который он не может обработать SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM..

Оба этих случая дают мне HttpRequestException, но я могу посмотреть ответное сообщение с сервера и определить причину исключения. Если это ошибка тайм-аута, я должен попробовать еще раз. Если это неправильный ввод, я должен повторно выбросить исключение, потому что никакие повторные попытки не решат проблему с неверными данными.

Что я хотел бы сделать с Полли, так это проверить ответное сообщение, прежде чем пытаться повторить попытку. Но все образцы, которые я видел до сих пор, включали только тип исключения.

Я до сих пор придумал это:

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

Есть ли лучший способ сделать такую ​​проверку?


person trailmax    schedule 13.06.2018    source источник


Ответы (1)


В общем, вы можете настроить политики Polly для ответа на результаты выполнения (а не только на исключение), например, проверьте HttpResponseMessage.StatusCode с помощью предиката. Примеры здесь, в Polly readme.

Однако не существует встроенного способа настроить единую политику Polly для дополнительного ответа на содержимое ответного сообщения. Это потому, что (как показывает ваш пример) для получения этого контента требуется второй асинхронный вызов, который сам по себе может вызвать сетевые ошибки.

Этот tl; dr вызывает сложности с тем, как выразить (в простом синтаксисе) единую политику, которая управляет двумя разными асинхронными шагами с потенциально различной обработкой ошибок для каждого шага. Предыдущее связанное обсуждение на Polly github: комментарий приветствуется.

Таким образом, если последовательность требует двух отдельных асинхронных вызовов, команда Polly в настоящее время рекомендует выражать это как две отдельные политики, аналогичные пример в конце этого ответа.


Конкретный пример в вашем вопросе может не работать, потому что делегат onRetryAsync (бросающий FlakyApiException) сам не охраняется политикой. Политика защищает только выполнение делегатов, выполняемых через .Execute/ExecuteAsync(...).


Один из подходов может заключаться в использовании двух политик: политики повтора, которая повторяет все типичные исключения http, и коды состояния, включая 500; затем внутри этого Polly FallbackPolicy, который перехватывает код состояния 500, представляющий SqlDateTime overflow, и исключает это от повторной попытки повторной генерации в качестве отличительного исключения (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));
person mountain traveller    schedule 13.06.2018
comment
Спасибо, что нашли время написать это. Звучит намного сложнее, чем я ожидал. Позвольте мне обернуть голову вокруг и попробовать - person trailmax; 13.06.2018