4
votes

I have an internal corporate API that I am calling via an HttpClient from a dotnet core website. The API is hosted in an IIS website that has Windows Authentication turned on. Authentication in the API is performed via this scheme. However the web requests are incredibly slow because the challenge-response process performed by the HttpClient contains large gaps of around 0.4 seconds between the 401 response being received from the API server and the HttpClient sending a subsequent (successful) request with the requisite Authorization header.

The HttpClient is normally injected via an HttpClientFactory but for the sake of clarity here is the equivalent inline code (the problem exists however the HttpClient is instantiated):

using (HttpClient httpClient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true }, true))
{
     httpClient.BaseAddress = new System.Uri( "https://localhost:60491/myapi/api/Internal/");
     var response = await httpClient.PutAsync(uri, content);

     await ValidateResponseAsync(response);
}

On the server the logs then show a 0.4 second gap between the 401 response to the initial request and the next request with the Authorization header:

15:56:09.80114 [INF] HTTP "PUT" "/api/Internal/Documents/989898989" responded 401 in 1.4533 ms. User null

15:56:10.17185 [INF] Request starting HTTP/1.1 PUT http://localhost:60491/myapi/api/Internal/Documents/989898989 application/json 750

This is not a network-related issue as the website is local. I have performed exactly the same request via a web browser and the difference between the 401 being received by the browser and the next request is .001 seconds. For the entire PUT request (incorporating both the 401 and 204 responses) from the browser it takes 0.05 seconds and from the HttpClient 0.4 seconds. The performance difference is entirely attributable to the gap in sending the request with the Authorization header.

I can't use the PreAuthenticate flag on the HttpClientHandler as this is Uri-specific and my requests contain IDs that will differ between each one.

Does anyone have any ideas why this would be occurring and, better still, how to get round it?

3
When you access via the browser, are you signed on the same way as the program that is using HttpClient (e.g. are you using the service account and not your personal login account)? There are a number of settings that might differ, e.g. proxy settings. Also, consider getting a certificate, as the TLS validation for localhost might be failing and falling back. Either that or use http:-- adding https for a localhost connection doesn't actually improve your security footing. - John Wu
The logins are the same in both cases. I have set UseProxy on the HttpClientHandler to false (no proxy is set up for the browser). The localhost website uses the ISS Express Development Certificate which is trusted. In any event this same behaviour occurs on our test servers too so it's not specific to my local environment, - strickt01
You may have to set up WIreshark and watch the traffic. Put them side by side and look for differences. The browser could be performing some sort of caching or have some other trick to improve throughput. - John Wu

3 Answers

1
votes

By default HttpClientHandler has the property UseProxy enabled by default. I have run in to similar situations where it was trying to find a proxy using the system default setting, set to "auto configue proxy", adding significant delay.

When you initialize HttpClientHandler set UseProxy to false and it may make the problem go away.

0
votes

Can't you just set a faster timeout? https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.timeout?redirectedfrom=MSDN&view=netframework-4.7.2

I'm assuming somehow this gets encoded in the request as well as used internally in the client. I have read that .net Core httpClient is very slow in 2.0, much faster in 2.1, you could also check the proxy settings as this can be a problem in .Net Core httpClient.

-2
votes

You should not be creating a new HttpClient per request, which is what your using statement is doing.

https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/calling-a-web-api-from-a-net-client

HttpClient is intended to be instantiated once and reused throughout the life of an application.