Avoid DNS issues with HttpClient in .NET
HttpClient
allows to send HTTP requests. HttpClient
is intended to be instantiated once and re-used throughout the life of an application. Indeed, the HttpClient
has a connection pool to reuse connections and reduce the number of TCP connections. So, if you send multiple requests to the same host, they will reuse the same connection. This way the application won't exhaust the number of sockets available under heavy loads (You're using HttpClient wrong and it's destabilizing your software). Also, this improves the performance of the application by avoiding handshakes (TCP handshake, TLS handshake) for each request to the same host.
Keeping connections opened is a good thing in terms of performance, but you have to be careful to not keep stale connections. What if the host has changed its IP address? For instance, if the DNS TTL is expired, the host may change its IP address. In this case, the opened connections should be closed and a new connection should be opened. The HttpClient
doesn't do this automatically as it has no knowledge about the DNS TTL. Instead, you can provide timeouts to automatically close the connection. This way, the next request will need to reopen a connection and the DNS will be used to find the new IP address.
You can use the SocketsHttpHandler
to configure the behavior of the HttpClient
and its connection pool. There are 2 properties to configure: PooledConnectionIdleTimeout
and PooledConnectionLifetime
. These properties allow to force the HttpClient
to close the connection after a certain amount of time. This way, the next request to the same host will need to open a new connection and so, to reflect the DNS or other network changes.
By default, idle connections are closed after 1 minute. However, active connections are never closed. You have to explicitly set PooledConnectionLifetime
to the desired value.
using System.Net;
using var socketHandler = new SocketsHttpHandler()
{
// The maximum idle time for a connection in the pool. When there is no request in
// the provided delay, the connection is released.
// Default value in .NET 6: 1 minute
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1),
// This property defines maximal connection lifetime in the pool regardless
// of whether the connection is idle or active. The connection is reestablished
// periodically to reflect the DNS or other network changes.
// ⚠️ Default value in .NET 6: never
// Set a timeout to reflect the DNS or other network changes
PooledConnectionLifetime = TimeSpan.FromMinutes(1),
};
using var httpClient = new HttpClient(socketHandler);
var timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
while (await timer.WaitForNextTickAsync())
{
_ = await httpClient.GetStringAsync("https://www.meziantou.net");
}
#Debugging
If you want to know when the HttpClient
instance is querying the DNS, you can use an EventListener
. Indeed, the System.Net.*
objects emit ETW traces.
using System.Diagnostics.Tracing;
_ = new NetEventListener();
using var socketHandler = new SocketsHttpHandler()
{
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1),
PooledConnectionLifetime = TimeSpan.FromSeconds(10),
};
using var httpClient = new HttpClient(socketHandler);
var timer = new PeriodicTimer(TimeSpan.FromSeconds(2));
while (await timer.WaitForNextTickAsync())
{
_ = await httpClient.GetStringAsync("https://www.meziantou.net");
}
class NetEventListener : EventListener
{
protected override void OnEventSourceCreated(EventSource eventSource)
{
if (eventSource.Name.StartsWith("System.Net"))
EnableEvents(eventSource, EventLevel.Informational);
}
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
if (eventData.EventName == "ResolutionStart")
{
Console.WriteLine(eventData.EventName + " - " + eventData.Payload[0]);
}
else if (eventData.EventName == "RequestStart")
{
Console.WriteLine(eventData.EventName + " - " + eventData.Payload[1]);
}
}
}
When you run this application, you should see when the application is doing http requests and DNS requests:
#Additional resources
- Tutorial: Make HTTP requests in a .NET console app using C#
- You're using HttpClient wrong and it's destabilizing your software
- Singleton HttpClient doesn't respect DNS changes
- Use IHttpClientFactory to implement resilient HTTP requests
Do you have a question or a suggestion about this post? Contact me!