Instantly share code, notes, and snippets.

Embed
What would you like to do?
TimeoutHandler for smarter timeout handing with HttpClient
public static class HttpRequestExtensions
{
private static string TimeoutPropertyKey = "RequestTimeout";
public static void SetTimeout(this HttpRequestMessage request, TimeSpan? timeout)
{
if (request == null) throw new ArgumentNullException(nameof(request));
request.Properties[TimeoutPropertyKey] = timeout;
}
public static TimeSpan? GetTimeout(this HttpRequestMessage request)
{
if (request == null) throw new ArgumentNullException(nameof(request));
if (request.Properties.TryGetValue(TimeoutPropertyKey, out var value) && value is TimeSpan timeout)
return timeout;
return null;
}
}
public class TimeoutHandler : DelegatingHandler
{
public TimeSpan DefaultTimeout { get; set; } = TimeSpan.FromSeconds(100);
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
using (var cts = GetCancellationTokenSource(request, cancellationToken))
{
try
{
return await base.SendAsync(request, cts?.Token ?? cancellationToken);
}
catch(OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
{
throw new TimeoutException();
}
}
}
private CancellationTokenSource GetCancellationTokenSource(HttpRequestMessage request, CancellationToken cancellationToken)
{
var timeout = request.GetTimeout() ?? DefaultTimeout;
if (timeout == Timeout.InfiniteTimeSpan)
{
// No need to create a CTS if there's no timeout
return null;
}
else
{
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(timeout);
return cts;
}
}
}
async Task TestAsync()
{
var handler = new TimeoutHandler
{
DefaultTimeout = TimeSpan.FromSeconds(10),
InnerHandler = new HttpClientHandler()
};
using (var cts = new CancellationTokenSource())
using (var client = new HttpClient(handler))
{
client.Timeout = Timeout.InfiniteTimeSpan;
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:8888/");
// Uncomment to test per-request timeout
//request.SetTimeout(TimeSpan.FromSeconds(5));
// Uncomment to test that cancellation still works properly
//cts.CancelAfter(TimeSpan.FromSeconds(2));
using (var response = await client.SendAsync(request, cts.Token))
{
Console.WriteLine(response.StatusCode);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment