使用 Polly 时收到错误 'The request message was already sent'

Receiving error 'The request message was already sent' when using Polly

我目前正在使用 Polly 来限制我发送的请求数量。这是我目前的政策:

private AsyncPolicyWrap<HttpResponseMessage> DefineAndRetrieveResiliencyStrategy()
{
    HttpStatusCode[] retryCodes = {
       HttpStatusCode.InternalServerError,
       HttpStatusCode.BadGateway,
       HttpStatusCode.GatewayTimeout
    };

    var waitAndRetryPolicy = Policy
        .HandleResult<HttpResponseMessage>(e => e.StatusCode == HttpStatusCode.ServiceUnavailable || e.StatusCode == (HttpStatusCode)429)
        .WaitAndRetryAsync(10,
            attempt => TimeSpan.FromSeconds(5), (exception, calculatedWaitDuration) =>
            {
                _log.Info($"Bitfinex API server is throttling our requests. Automatically delaying for {calculatedWaitDuration.TotalMilliseconds}ms");
            }
        );

    var circuitBreakerPolicyForRecoverable = Policy
        .Handle<HttpResponseException>()
        .OrResult<HttpResponseMessage>(r => retryCodes.Contains(r.StatusCode))
        .CircuitBreakerAsync(
            handledEventsAllowedBeforeBreaking: 3,
            durationOfBreak: TimeSpan.FromSeconds(3),
            onBreak: (outcome, breakDelay) =>
            {
                _log.Info($"Polly Circuit Breaker logging: Breaking the circuit for {breakDelay.TotalMilliseconds}ms due to: {outcome.Exception?.Message ?? outcome.Result.StatusCode.ToString()}");

            },
            onReset: () => _log.Info("Polly Circuit Breaker logging: Call ok... closed the circuit again"),
            onHalfOpen: () => _log.Info("Polly Circuit Breaker logging: Half-open: Next call is a trial")
        );

    return Policy.WrapAsync(waitAndRetryPolicy, circuitBreakerPolicyForRecoverable);
}

我有以下请求发件人:

private async Task<string> SendRequest(GenericRequest request, string httpMethod, string publicKey, string privateKey)
{
    var resiliencyStrategy = DefineAndRetrieveResiliencyStrategy();

    using (var client = new HttpClient())
    using (var httpRequest = new HttpRequestMessage(new HttpMethod(httpMethod), request.request))
    {
        string json = JsonConvert.SerializeObject(request);
        string json64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(json));
        byte[] data = Encoding.UTF8.GetBytes(json64);

        client.BaseAddress = new Uri(Properties.Settings.Default.BitfinexUri);

        var hashMaker = new HMACSHA384(Encoding.UTF8.GetBytes(privateKey));
        byte[] hash = hashMaker.ComputeHash(data);
        string signature = GetHexString(hash);

        httpRequest.Headers.Add("X-BFX-APIKEY", publicKey);
        httpRequest.Headers.Add("X-BFX-PAYLOAD", json64);
        httpRequest.Headers.Add("X-BFX-SIGNATURE", signature);

        var message = await resiliencyStrategy.ExecuteAsync(() => client.SendAsync(httpRequest));
        var response = message.Content.ReadAsStringAsync().Result;

        return response;
    }
}

一旦代码触发 waitAndRetryPolicy 并等待所需的时间,我就会收到以下错误:

System.InvalidOperationException: 'The request message was already sent. Cannot send the same request message multiple times.'

我知道这是因为我再次发送相同的 HttpRequest 但 Polly 库不应该处理这样的问题吗?

那个异常:

System.InvalidOperationException: 'The request message was already sent. Cannot send the same request message multiple times.'

thrown by the internals of HttpClient if you call directly into any .SendAsync(...) overload with an HttpRequestMessage which has already been sent

如果您使用的是.NET Core,建议的解决方案是使用Polly with HttpClientFactory: this solves the above exception by executing the policy (for example retry) via a DelegatingHandler within HttpClient. It also solves the socket-exhaustion problem,这可能是由HttpClient的频繁create/dispose引起的,代码在问题中发布可能容易受到攻击。

如果您使用 .NET 框架,建议的解决方案是:

  • 复制 HttpClientFactory 在 DelegatingHandler 中放置策略的方式;
  • 重构您的代码以在通过策略执行的代码中制造 HttpRequestMessage 的新实例(或克隆现有实例)。

This Whosebug question 广泛讨论了该问题以及上述解决方案的许多变体。