英文:
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
是由 HttpClient
的 CheckRequestMessage
方法抛出。
这个方法被 CheckRequestBeforeSend
调用。这是 SendAsync
的第一个命令。此公共 SendAsync
调用其 base
的 SendAsync
。这里的 base
是一个处理 HttpMessageHandler
的 HttpMessageInvoker
。
长话短说:
- 如果你想在
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 theHttpClient
then it will fail at the second attempt due to theCheckRequestMessage
call -
If you want to reuse the
HttpRequestMessage
inside aDelegatingHandler
(HttpMessageHandler
) then it won't fail because theHttpMessageInvoker
does not care about the reuse
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论