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?
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:
HttpRequestMessage
outside the HttpClient
then it will fail at the second attempt due to the CheckRequestMessage
callHttpRequestMessage
inside a DelegatingHandler
(HttpMessageHandler
) then it won't fail because the HttpMessageInvoker
does not care about the reuse