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