Reputation: 1541
I've read a few articles and the pitfalls of using a static HttpClient and the solution. One of the articles being - http://byterot.blogspot.ca/2016/07/singleton-httpclient-dns.html
I've implemented the solution and want to test to ensure that what the article proposed will actually work.
The following is the code that we are trying to avoid:
Task.Run(async () =>
{
using (var httpClient = new HttpClient(new ApiHandler()))
{
var httpResponse = await httpClient.GetAsync("https://test.com/api/Tiers");
var result = await httpResponse.Content.ReadAsStringAsync();
Console.WriteLine(result);
}
}).ContinueWith(async task =>
{
await Task.Delay(5000);
using (var httpClient = new HttpClient(new ApiHandler()))
{
var httpResponse = await httpClient.GetAsync("https://test.com/api/Tiers");
var result = await httpResponse.Content.ReadAsStringAsync();
Console.WriteLine(result);
}
}).ContinueWith(async task =>
{
await Task.Delay(10000);
using (var httpClient = new HttpClient(new ApiHandler()))
{
var httpResponse = await httpClient.GetAsync("https://test.com/api/Tiers");
var result = await httpResponse.Content.ReadAsStringAsync();
Console.WriteLine(result);
}
});
When I view the traffic in fiddler:
The behavior is as expect. Each create and dispose forces a connection to be established when the the request and response are completed.
The proper and proposed way to use the HttpClient is to for it to be static:
private static readonly HttpClient HttpClient = new HttpClient(new ApiHandler());
static void Main()
{
ServicePointManager
.FindServicePoint(new Uri("https://test.com"))
.ConnectionLeaseTimeout = 1000; // 1 second
ServicePointManager.DnsRefreshTimeout = 1000; // 1 second
Task.Run(async () =>
{
var httpResponse = await HttpClient.GetAsync("https://test.com/api/Tiers");
var result = await httpResponse.Content.ReadAsStringAsync();
Console.WriteLine(result);
}).ContinueWith(async task =>
{
await Task.Delay(5000); // delay 5 seconds
var httpResponse = await HttpClient.GetAsync("https://test.com/api/Tiers");
var result = await httpResponse.Content.ReadAsStringAsync();
Console.WriteLine(result);
}).ContinueWith(async task =>
{
await Task.Delay(10000); // delay 10 seconds
var httpResponse = await HttpClient.GetAsync("https://test.com/api/Tiers");
var result = await httpResponse.Content.ReadAsStringAsync();
Console.WriteLine(result);
});
Console.ReadKey();
}
I'd expect the same behavior as the previous image however it looks like the following:
And if I then add HttpClient.DefaultRequestHeaders.ConnectionClose = true;
I get the desired outcome however this is what we want to avoid - for each request response to create a connection.
So is my desired outcome in Fiddler correct? or am I missing something with setting the ConnectionLeaseTimeout
and/or DnsRefreshTimeout
? I'd really like to test this behavior and ensure that setting those properties on ServicePointManager
resolves the know DNS issue with a static instance of HttpClient.
Upvotes: 4
Views: 4706
Reputation: 101483
Fiddler registers itself as a proxy. By default it listens on 127.0.0.1, port 8888. So to get correct service point object for your case you have to do this:
ServicePointManager.FindServicePoint(
new Uri("https://test.com"),
new WebProxy(new Uri("http://127.0.0.1:8888")))
.ConnectionLeaseTimeout = 1000; // 1 second
Now you will see correct outcome. Note that it will be not exactly as you expect. First request will open new connection. Second request will not open new connection, even though 5 seconds has passed. Instead, it will set Connection: close
header on request, indicating that connection should be closed. Then, next request (third) will finally initiate new connection. This is expected behavior as far as I can tell for ConnectionLeaseTimeout
.
Upvotes: 4