First let's define a mock server which does respond with 500 after 100 seconds:
const string address = "http://localhost:9000";
var delay = TimeSpan.FromSeconds(100);
var server = WireMockServer.Start(new WireMockServerSettings { Urls = new[] { address } });
server
.Given(Request.Create().WithPath("/").UsingPost())
.RespondWith(Response.Create().WithDelay(delay).WithStatusCode(500));
I've used WireMock.Net for this.
Now, let's see the IDiscoveryClient
and DiscoveryClient
:
interface IDiscoveryClient
{
Task<TResponse> SendRequest<TResponse, TPostData>(string url, TPostData data);
}
class DiscoveryClient : IDiscoveryClient
{
private readonly HttpClient httpClient;
public DiscoveryClient(HttpClient httpClient) => this.httpClient = httpClient;
public async Task<TResponse> SendRequest<TResponse, TPostData>(string url, TPostData data)
{
var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8);
var response = await httpClient.PostAsync(url, content);
response.EnsureSuccessStatusCode();
var rawData = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<TResponse>(rawData);
}
}
class TestRequest { public string Content { get; set; } }
class TestResponse { public string Data { get; set; } }
I've used json instead of xml, but that's not imporant from the question point of view.
And finally let's wire up the DI and issue a request:
AsyncTimeoutPolicy<HttpResponseMessage> timeoutPolicy =
Policy.TimeoutAsync<HttpResponseMessage>(5, TimeoutStrategy.Pessimistic);
IServiceCollection services = new ServiceCollection();
services.AddHttpClient<IDiscoveryClient, DiscoveryClient>()
.AddPolicyHandler(timeoutPolicy);
ServiceProvider serviceProvider = services.BuildServiceProvider();
var client = serviceProvider.GetService<IDiscoveryClient>();
Stopwatch sw = Stopwatch.StartNew();
try
{
TestResponse res = await client.SendRequest<TestResponse, TestRequest>(address, new TestRequest { Content = "Test"});
}
catch (TimeoutRejectedException ex)
{
sw.Stop();
Console.WriteLine(sw.Elapsed);
}
The printed output will be something like this:
00:00:05.0296804
The good thing is that it does work with Optimistic
and Pessimistic
strategies as well.
await httpClient.PostAsXmlAsync(url, postData)
or atawait response.Content.ReadAsAsync<TResponse>()
? How are you seeing the timeout? In logs, or are you seeing a timeout thrown as an exception from either of those two code lines? – mountain travellerawait httpClient.PostAsXmlAsync(url, postData)
is taking 100s to complete and then I receive aHttpRequestException: Response status code does not indicate success: 500 (Internal Server Error).
fromresponse.EnsureSuccessStatusCode();
The service I'm calling is known to do this from time to time which is why I wanted to implement the Polly Timeout policy. – wdspiderTimeoutStrategy.Optimistic
? HttpClientFactory definitely transmits the cancellationtoken downstream, so I would have expectedTimeoutStrategy.Optimistic
to be adequate. Otherwise we will need to construct a minimal reproducible example . – mountain travellerTimeoutStrategy.Optimistic
doesn't seem to matter. I created a demo here: gist.github.com/wdspider/a1cf8328dbcf6cd42ea0a889f5427f0b The slow responding web service that I'm calling is not public; therefore, I simply added a 20s delay in theClientLoggingHandler
which should be a decent simulation. – wdspiderawait Task.Delay(...)
executes before (is 'outside', in terms of nesting) theTimeoutPolicy
; ie the timeout policy doesn't govern the delay. Reverse it toservices....AddPolicyHandler(timeoutPolicy).AddHttpMessageHandler<ClientLoggingHandler>();
and the timeout works. Does this info help the original case? – mountain traveller