TheRock
TheRock

Reputation: 1541

HttpClient - DNS changes are NOT honoured

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:

enter image description here

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:

enter image description here

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

Answers (1)

Evk
Evk

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

Related Questions