Search code examples
c#dotnet-httpclienthttpcontent

Retrying HttpClient Unsuccessful Requests


I am building a function that given an HttpContent Object, will issues request and retry on failure. However I get exceptions saying that HttpContent Object is disposed after issuing the request. Is there anyway to copy or duplicate the HttpContent Object so that I can issue multiple requests.

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

(Obviously I don't try indefinitely but the code above is essentially what i want).

It yields this exception

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)

Is there anyway to duplicate an HttpContent Object or reuse it?


Solution

  • Instead of implementing retry functionality that wraps the HttpClient, consider constructing the HttpClient with a HttpMessageHandler that performs the retry logic internally. For example:

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