Проверьте содержимое строки ответа перед повторной попыткой с Полли - PullRequest
0 голосов
/ 13 июня 2018

Я работаю с очень нестабильным 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);
                }
            });

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

1 Ответ

0 голосов
/ 13 июня 2018

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

Однако нет встроенного встроенного способа настройки политики single 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));
...