Подтвердить что ты не робот

Повторная попытка неудачных запросов HttpClient

Я создаю функцию, которая задает объект HttpContent, выдает запрос и повторяет попытку. Однако я получаю исключения, говорящие, что объект HttpContent удаляется после выдачи запроса. Нужно ли копировать или дублировать объект HttpContent, чтобы я мог выдавать несколько запросов.

 public HttpResponseMessage ExecuteWithRetry(string url, HttpContent content)
 {
  HttpResponseMessage result = null;
  bool success = false;
  do
  {
      using (var client = new HttpClient())
      {
          result = client.PostAsync(url, content).Result;
          success = result.IsSuccessStatusCode;
      }
  }
  while (!success);

 return result;
} 

// Works with no exception if first request is successful
ExecuteWithRetry("http://www.requestb.in/xfxcva" /*valid url*/, new StringContent("Hello World"));
// Throws if request has to be retried ...
ExecuteWithRetry("http://www.requestb.in/badurl" /*invalid url*/, new StringContent("Hello World"));

(Очевидно, я не пробовал бесконечно, но вышеприведенный код - это, по сути, то, что я хочу).

Он дает это исключение

System.AggregateException: One or more errors occurred. ---> System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Net.Http.StringContent'.
   at System.Net.Http.HttpContent.CheckDisposed()
   at System.Net.Http.HttpContent.CopyToAsync(Stream stream, TransportContext context)
   at System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar)
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
   at System.Threading.Tasks.Task`1.get_Result()
   at Submission#8.ExecuteWithRetry(String url, HttpContent content)

Нужно ли дублировать объект HttpContent или повторно использовать его?

4b9b3361

Ответ 1

Вместо того, чтобы реализовывать функциональность повторения, которая оборачивает HttpClient, рассмотрите возможность создания HttpClient с HttpMessageHandler который выполняет логику повторения внутри. Например:

public class RetryHandler : DelegatingHandler
{
    // Strongly consider limiting the number of retries - "retry forever" is
    // probably not the most user friendly way you could respond to "the
    // network cable got pulled out."
    private const int MaxRetries = 3;

    public RetryHandler(HttpMessageHandler innerHandler)
        : base(innerHandler)
    { }

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        HttpResponseMessage response = null;
        for (int i = 0; i < MaxRetries; i++)
        {
            response = await base.SendAsync(request, cancellationToken);
            if (response.IsSuccessStatusCode) {
                return response;
            }
        }

        return response;
    }
}

public class BusinessLogic
{
    public void FetchSomeThingsSynchronously()
    {
        // ...

        // Consider abstracting this construction to a factory or IoC container
        using (var client = new HttpClient(new RetryHandler(new HttpClientHandler())))
        {
            myResult = client.PostAsync(yourUri, yourHttpContent).Result;
        }

        // ...
    }
}

Ответ 2

ASP.NET Core 2.1 Ответ

В ASP.NET Core 2.1 добавлена поддержка Полли напрямую. Здесь UnreliableEndpointCallerService - это класс, который принимает HttpClient в своем конструкторе. Неудачные запросы будут повторяться с экспоненциальной задержкой, поэтому следующая повторная попытка будет происходить через экспоненциально более длительное время после предыдущей:

services
    .AddHttpClient<UnreliableEndpointCallerService>()
    .AddTransientHttpErrorPolicy(
        x => x.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(3, retryAttempt)));

Также рассмотрите возможность прочтения моего блога "Оптимальная настройка HttpClientFactory".

Ответ других платформ

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

public class HttpRetryMessageHandler : DelegatingHandler
{
    public HttpRetryMessageHandler(HttpClientHandler handler) : base(handler) {}

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken) =>
        Policy
            .Handle<HttpRequestException>()
            .Or<TaskCanceledException>()
            .OrResult<HttpResponseMessage>(x => !x.IsSuccessStatusCode)
            .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(3, retryAttempt)))
            .ExecuteAsync(() => base.SendAsync(request, cancellationToken));
}

using (var client = new HttpClient(new HttpRetryMessageHandler(new HttpClientHandler())))
{
    var result = await client.GetAsync("http://example.com");
}

Ответ 3

Текущие ответы не будут работать должным образом во всех случаях, особенно в очень распространенном случае истечения времени ожидания запроса (см. Мои комментарии там).

Кроме того, они реализуют очень наивную стратегию повторных попыток - во многих случаях вам нужно что-то более сложное, например, экспоненциальный откат (который используется по умолчанию в клиентском API хранилища Azure).

Я наткнулся на TOPAZ, читая сообщение в соответствующем блоге (также предлагая ошибочный внутренний подход к повторной попытке). Вот что я придумала:

// sample usage: var response = await RequestAsync(() => httpClient.GetAsync(url));
Task<HttpResponseMessage> RequestAsync(Func<Task<HttpResponseMessage>> requester)
{
    var retryPolicy = new RetryPolicy(transientErrorDetectionStrategy, retryStrategy);
    //you can subscribe to the RetryPolicy.Retrying event here to be notified 
    //of retry attempts (e.g. for logging purposes)
    return retryPolicy.ExecuteAsync(async () =>
    {
        HttpResponseMessage response;
        try
        {
            response = await requester().ConfigureAwait(false);
        }
        catch (TaskCanceledException e) //HttpClient throws this on timeout
        {
            //we need to convert it to a different exception
            //otherwise ExecuteAsync will think we requested cancellation
            throw new HttpRequestException("Request timed out", e);
        }
        //assuming you treat an unsuccessful status code as an error
        //otherwise just return the respone here
        return response.EnsureSuccessStatusCode(); 
    });
}

Обратите внимание на параметр делегата requester. Это не должно быть HttpRequestMessage поскольку вы не можете отправлять один и тот же запрос несколько раз. Что касается стратегий, это зависит от вашего варианта использования. Например, стратегия обнаружения временных ошибок может быть такой простой, как:

private sealed class TransientErrorCatchAllStrategy : ITransientErrorDetectionStrategy
{
    public bool IsTransient(Exception ex)
    {
        return true;
    }
}

Что касается стратегии повтора, ТОПАЗ предлагает три варианта:

  1. FixedInterval
  2. дополнительный
  3. экспоненциальная выдержка

Например, здесь эквивалент TOPAZ того, что по умолчанию клиентская библиотека хранилища Azure использует:

int retries = 3;
var minBackoff = TimeSpan.FromSeconds(3.0);
var maxBackoff = TimeSpan.FromSeconds(120.0);
var deltaBackoff= TimeSpan.FromSeconds(4.0);
var strategy = new ExponentialBackoff(retries, minBackoff, maxBackoff, deltaBackoff);

Для получения дополнительной информации см. Http://msdn.microsoft.com/en-us/library/hh680901(v=pandp.50).aspx

РЕДАКТИРОВАТЬ Обратите внимание, что если ваш запрос содержит объект HttpContent, вам придется регенерировать его каждый раз, так как он будет также HttpClient (спасибо, что поймал этого Александра Пепина). Например () => httpClient.PostAsync(url, new StringContent("foo"))).

Ответ 4

Дублирование StringContent, вероятно, не лучшая идея. Но простая модификация может решить проблему. Просто измените функцию и создайте объект StringContent внутри цикла, например:

public HttpResponseMessage ExecuteWithRetry(string url, string contentString)
{
   HttpResponseMessage result = null;
   bool success = false;
   using (var client = new HttpClient())
   {
      do
      {
         result = client.PostAsync(url, new StringContent(contentString)).Result;
         success = result.IsSuccessStatusCode;
      }
      while (!success);
  }    

  return result;
} 

а затем назовите его

ExecuteWithRetry("http://www.requestb.in/xfxcva" /*valid url*/, "Hello World");

Ответ 5

У меня почти такая же проблема. Библиотека запросов HttpWebRequest, гарантирующая доставку запросов Я только что обновил (см. EDIT3) мой подход, чтобы избежать сбоев, но мне по-прежнему нужен общий механизм, гарантирующий доставку сообщений (или повторная доставка в случае, если сообщение не было доставлено).

Ответ 6

Я попробовал и работал во время использования модулей и тестов интеграции. Тем не менее, он застрял, когда я действительно звонил с URL REST. Я нашел этот интересный пост, который объясняет, почему он застрял в этой строке.

response = await base.SendAsync(request, cancellationToken);

Исправлено это то, что вы добавили .ConfigureAwait(false) в конце.

response = await base.SendAsync(request, token).ConfigureAwait(false);

Я также добавил создать связанную часть токена там, как это.

var linkedToken = cancellationToken.CreateLinkedSource();
linkedToken.CancelAfter(new TimeSpan(0, 0, 5, 0));
var token = linkedToken.Token;

HttpResponseMessage response = null;
for (int i = 0; i < MaxRetries; i++)
{
    response = await base.SendAsync(request, token).ConfigureAwait(false);
    if (response.IsSuccessStatusCode)
    {
        return response;
    }
}

return response;

Ответ 7

С RestEase And Task, при повторной попытке с использованием httpClient, повторно используемого во многих вызовах (singleton), он блокируется и генерирует исключение TaskCanceledException. Чтобы исправить это, нужно Dispose() неудачный ответ перед повторной попыткой

public class RetryHandler : DelegatingHandler
{
    // Strongly consider limiting the number of retries - "retry forever" is
    // probably not the most user friendly way you could respond to "the
    // network cable got pulled out."
    private const int MaxRetries = 3;

    public RetryHandler(HttpMessageHandler innerHandler)
        : base(innerHandler)
    { }

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        HttpResponseMessage response = null;
        for (int i = 0; i < MaxRetries; i++)
        {
            response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
            if (response.IsSuccessStatusCode) {
                return response;
            }

            response.Dispose();
        }

        return response;
    }
}

Ответ 8

Вы также обратитесь к разделу "Создание обработчика временного повтора для .NET HttpClient". Посетите ссылку на сообщение KARTHIKEYAN VIJAYAKUMAR.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SqlClient;
using System.Net.Http;
using System.Threading;
using System.Diagnostics;
using System.Net;
using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling;

namespace HttpClientRetyDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            var url = "http://RestfulUrl";
            var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);

            var handler = new RetryDelegatingHandler
            {
                UseDefaultCredentials = true,
                PreAuthenticate = true,
                Proxy = null
            };

            HttpClient client = new HttpClient(handler);
            var result = client.SendAsync(httpRequestMessage).Result.Content
                .ReadAsStringAsync().Result;

            Console.WriteLine(result.ToString());
            Console.ReadKey();

        }
    }

    /// <summary>
    /// Retry Policy = Error Detection Strategy + Retry Strategy
    /// </summary>
    public static class CustomRetryPolicy
    {
        public static RetryPolicy MakeHttpRetryPolicy()
        {
            // The transient fault application block provides three retry policies
            //  that you can use. These are:
            return new RetryPolicy(strategy, exponentialBackoff);
        }
    }

    /// <summary>
    /// This class is responsible for deciding whether the response was an intermittent
    /// transient error or not.
    /// </summary>
    public class HttpTransientErrorDetectionStrategy : ITransientErrorDetectionStrategy
    {
        public bool IsTransient(Exception ex)
        {
            if (ex != null)
            {
                HttpRequestExceptionWithStatus httpException;
                if ((httpException = ex as HttpRequestExceptionWithStatus) != null)
                {
                    if (httpException.StatusCode == HttpStatusCode.ServiceUnavailable)
                    {
                        return true;
                    }
                    else if (httpException.StatusCode == HttpStatusCode.MethodNotAllowed)
                    {
                        return true;
                    }
                    return false;
                }
            }
            return false;
        }
    }

    /// <summary>
    /// The retry handler logic is implementing within a Delegating Handler. This has a
    /// number of advantages.
    /// An instance of the HttpClient can be initialized with a delegating handler making
    /// it super easy to add into the request pipeline.
    /// It also allows you to apply your own custom logic before the HttpClient sends the
    /// request, and after it receives the response.
    /// Therefore it provides a perfect mechanism to wrap requests made by the HttpClient
    /// with our own custom retry logic.
    /// </summary>
    class RetryDelegatingHandler : HttpClientHandler
    {
        public RetryPolicy retryPolicy { get; set; }
        public RetryDelegatingHandler()
            : base()
        {
            retryPolicy = CustomRetryPolicy.MakeHttpRetryPolicy();
        }


        protected async override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            HttpResponseMessage responseMessage = null;
            var currentRetryCount = 0;
            //On Retry => increments the retry count
            retryPolicy.Retrying += (sender, args) =>
            {
                currentRetryCount = args.CurrentRetryCount;
            };
            try
            {
                await retryPolicy.ExecuteAsync(async () =>
                {
                    responseMessage = await base.SendAsync(request, cancellationToken)
                        .ConfigureAwait(false);
                    if ((int)responseMessage.StatusCode > 500)
                    {
                        // When it fails after the retries, it would throw the exception
                        throw new HttpRequestExceptionWithStatus(
                            string.Format("Response status code {0} indicates server error",
                                (int)responseMessage.StatusCode))
                        {
                            StatusCode = responseMessage.StatusCode,
                            CurrentRetryCount = currentRetryCount
                        };
                    }// returns the response to the main method(from the anonymous method)
                    return responseMessage;
                }, cancellationToken).ConfigureAwait(false);
                return responseMessage;// returns from the main method => SendAsync
            }
            catch (HttpRequestExceptionWithStatus exception)
            {
                if (exception.CurrentRetryCount >= 3)
                {
                    //write to log
                }
                if (responseMessage != null)
                {
                    return responseMessage;
                }
                throw;
            }
            catch (Exception)
            {
                if (responseMessage != null)
                {
                    return responseMessage;
                }
                throw;
            }
        }
    }

    /// <summary>
    /// Custom HttpRequestException to allow include additional properties on my exception,
    /// which can be used to help determine whether the exception is a transient
    /// error or not.
    /// </summary>
    public class HttpRequestExceptionWithStatus : HttpRequestException
    {
        public HttpStatusCode StatusCode { get; set; }
        public int CurrentRetryCount { get; set; }

        public HttpRequestExceptionWithStatus()
            : base() { }

        public HttpRequestExceptionWithStatus(string message)
            : base(message) { }

        public HttpRequestExceptionWithStatus(string message, Exception inner)
            : base(message, inner) { }
    }
}

Ответ 9

        //Could retry say 5 times          
        HttpResponseMessage response;
        int numberOfRetry = 0;
        using (var httpClient = new HttpClient())
        {
            do
            {
                response = await httpClient.PostAsync(uri, content);
                numberOfRetry++;
            } while (response.IsSuccessStatusCode == false | numberOfRetry < 5);
        }
return response;



        .........