Повторная Попытка 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 или повторно использовать его?

8 ответов


вместо реализации функции повтора, которая обертывает 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;
        }

        // ...
    }
}

ASP.NET Core 2.1 ответ

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

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

также советуем Вам прочитать мой блог "Оптимально Настройка HttpClientFactory".

Другие Платформы Отвечают

эта реализация использует Поли повторить попытку с экспоненциальным отступлением, чтобы следующая попытка состоялась в экспоненциально более длительное время после предыдущей. Он также повторяет попытку, если 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");
}

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

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

я наткнулся на топаз во время чтения блоги (также ошибочный внутренний повторный подход). Вот что я придумал:--9-->

// 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 параметр делегата. Он должен!--19-->не быть HttpRequestMessage так как вы не можете отправить один и тот же запрос несколько раз. Что касается стратегий, это зависит от вашего варианта использования. Например, стратегия обнаружения временных ошибок может быть такой простой, как:

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

Что касается стратегии повтора, TOPAZ предлагает три опции:

  1. FixedInterval
  2. добавочные
  3. ExponentialBackoff

например, вот топазовый эквивалент того, что клиентская библиотека хранения 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"))).


дублирование 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");

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


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

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;

вы также ссылаетесь на создание временного обработчика повтора для .NET HttpClient посещать. см.КАРТИКЕЯН ВИДЖАЯКУМАР пост.

                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();
                    handler.UseDefaultCredentials = true;
                    handler.PreAuthenticate = true;
                    handler.Proxy = null;
                    HttpClient client = new HttpClient(handler);
                    var result = client.SendAsync(httpRequestMessage).Result.Content.ReadAsStringAsync().Result;
                    Console.WriteLine(result.ToString());
                    Console.ReadKey();

                }
                //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.
                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;
                    }
                    }
                }
                //Retry Policy = Error Detection Strategy + Retry Strategy
                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);
                    }
                }
                //This class is responsible for deciding whether the response was an intermittent transient error or not.
                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;
                    }
                }
                //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.
                public class HttpRequestExceptionWithStatus : HttpRequestException
                {
                    public HttpRequestExceptionWithStatus() : base() { }
                    public HttpRequestExceptionWithStatus(string message) : base(message) { }
                    public HttpRequestExceptionWithStatus(string message, Exception inner) : base(message, inner) { }
                    public HttpStatusCode StatusCode { get; set; }
                    public int CurrentRetryCount { get; set; }
                }
                }
            }

        //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;



        .........