Polly能够重新发送相同的HTTP请求吗?

huangapple go评论58阅读模式
英文:

How is Polly able to resend the same HTTP request?

问题

HttpRequestMessage只能发送一次。 尝试重新发送会导致InvalidOperationException

那么Polly是如何规避这种行为的呢,换句话说,使用Retry策略时,在AddPolicyHandler下发生了什么? 我明白它使用了DelegatingHandler,但它是如何多次处理相同消息的?

英文:

An HttpRequestMessage can only be sent once. Attempting to resend it results in an InvalidOperationException.

So how is Polly able to circumvent this behavior, in other words, what's going on under the covers when using AddPolicyHandler with a Retry policy? I understand that it uses a DelegatingHandler but how is it able to process the same message multiple times?

答案1

得分: 5

TL;DR: 与 Polly 无关。

让我们从一个单例失败的例子开始,第二次尝试会抛出“InvalidOperationException”异常。

如果我们将请求对象创建移到“for”循环内,一切都会正常。但让我们将其保留在“for”循环外。

让我们创建一个简单的委托处理程序并使用它。

这个版本不会抛出任何“InvalidOperationException”。

那么,为什么后者能工作而前者不行?

InvalidOperationException 是由 HttpClientCheckRequestMessage 方法抛出。

这个方法被 CheckRequestBeforeSend 调用。这是 SendAsync 的第一个命令。此公共 SendAsync 调用其 baseSendAsync。这里的 base 是一个处理 HttpMessageHandlerHttpMessageInvoker

长话短说:

  • 如果你想在 HttpClient 外重用 HttpRequestMessage,那么第二次尝试会失败,因为会调用 CheckRequestMessage
  • 如果你想在 DelegatingHandler (HttpMessageHandler) 内重用 HttpRequestMessage,它不会失败,因为 HttpMessageInvoker 不关心重用。
英文:

TL;DR: It has nothing to do with Polly.


Let's start with a single example which fails with an InvalidOperationException at the second attempt.

var request = new HttpRequestMessage(HttpMethod.Get, "https://httpstat.us/500");

var httpClient = new HttpClient(new HttpClientHandler());
for(int i = 0; i < 3; i++)
{
	var result = await httpClient.SendAsync(request);
	result.StatusCode.Dump();
}

If we would move the request object creation inside the for loop then everything would be fine. But let's keep it outside of the for.

Let's create a simple delegating handler

public class RetryHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpResponseMessage result = null;
		for(int i = 0; i < 3; i++)
		{
		 	result = await base.SendAsync(request, cancellationToken);
			"Retried".Dump();
		}
		return result;
    }
}

and let's use it

var handler = new RetryHandler();
handler.InnerHandler = new HttpClientHandler();

var httpClient = new HttpClient(handler);
var request = new HttpRequestMessage(HttpMethod.Get, "https://httpstat.us/500");

var result = await httpClient.SendAsync(request);
result.StatusCode.Dump();

This version won't throw any InvalidOperationException.


So, why does the latter work and former doesn't?

The InvalidOperationException is thrown by the CheckRequestMessage method of the HttpClient

private static void CheckRequestMessage(HttpRequestMessage request)
{
    if (!request.MarkAsSent())
    {
        throw new InvalidOperationException(SR.net_http_client_request_already_sent);
    }
}

This method is being called by the CheckRequestBeforeSend. And it is the very first command of the SendAsync. This public SendAsync calls its base's SendAsync. Here the base is a HttpMessageInvoker which deals with the HttpMessageHandler.

Long story short:

  • If you want to reuse the HttpRequestMessage outside the HttpClient then it will fail at the second attempt due to the CheckRequestMessage call

  • If you want to reuse the HttpRequestMessage inside a DelegatingHandler (HttpMessageHandler) then it won't fail because the HttpMessageInvoker does not care about the reuse

huangapple
  • 本文由 发表于 2023年4月20日 04:48:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/76058694.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定