Static HttpClient still creating TIME_WAIT tcp ports - c#

I am experiencing some interesting behavior with the HttpClient from the .NET Framework (4.5.1+, 4.6.1 and 4.7.2). I have proposed some changes in a project at work to not dispose of the HttpClient on each use because of the known issue with high TCP port usage, see https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/.
I have investigated the changes to check that things were working as expected and found that we are still experiencing the same TIME_WAIT ports as before.
To confirm that my proposed changes were correct I have added some extra tracing to the application that confirm that I am using the same instance of the HttpClient through out the application. I have since used simple test application (taken from the aspnetmonsters site linked above.
using System;
using System.Net.Http;
namespace ConsoleApplication
{
public class Program
{
private static HttpClientHandler { UseDefaultCredentials = true };
private static HttpClient Client = new HttpClient(handler);
public static async Task Main(string[] args)
{
Console.WriteLine("Starting connections");
for(int i = 0; i<10; i++)
{
var result = await Client.GetAsync("http://localhost:51000");
Console.WriteLine(result.StatusCode);
}
Console.WriteLine("Connections done");
Console.ReadLine();
}
}
}
The issue only occurs when connecting to a site that is hosted in IIS using Windows Authentication. I can reproduce the issue easily by setting the Authentication to Anonymous (problem goes away) and back to Windows Authentication (problem reoccurs).
The issue with Windows Authentication does not seem to be limited to the scope of the provider. It has the same issue if you us Negotiate or NTLM. Also the issue occurs if the machine is just a workstation or part of a domain.
Out of interest I created a dotnet core 2.1.0 console app and the issue is not present at all and works as expected.
TLDR: Does any one have any idea on how to fix this, or is it likely to be a bug?

Short version
Use .NET Core 2.1 if you want to reuse connections with NTLM authentication
Long version
I was quite surprised to see that the "old" HttpClient does use a different connection for each request when NTLM authentication is used. This isn't a bug - before .NET Core 2.1 HttpClient would use HttpWebRequest which closes the connection after every NTLM authenticated call.
This is described in the documentation of the HttpWebRequest.UnsafeAuthenticatedConnectionSharing property which can be used to enable sharing of the connection :
The default value for this property is false, which causes the current connection to be closed after a request is completed. Your application must go through the authentication sequence every time it issues a new request.
If this property is set to true, the connection used to retrieve the response remains open after the authentication has been performed. In this case, other requests that have this property set to true may use the connection without re-authenticating.
The risk is that :
If a connection has been authenticated for user A, user B may reuse A's connection; user B's request is fulfilled based on the credentials of user A.
If one understands the risks, and the application doesn't use impersonation, one could configure HttpClient with a WebRequestHandler and set the UnsafeAuthenticatedConnectionSharing, eg :
HttpClient _client;
public void InitTheClient()
{
var handler=new WebRequestHandler
{
UseDefaultCredentials=true,
UnsafeAuthenticatedConnectionSharing =true
};
_client=new HttpClient(handler);
}
WebRequestHandler doesn't expose the HttpWebRequest.ConnectionGroupName that would allow to group connections eg by ID, so it can't handle impersonation.
.NET Core 2.1
HttpClient was rewritten in .NET Core 2.1 and implements all the HTTP, networking functionality using sockets, minimal allocations, connection pooling etc. It also handles the NTLM challenge/response flow separatelly so the same socket connection can be used to serve different authenticated requests.
If anyone is interested, you can chase the calls from HttpClient to SocketsHttpHanlder to HttpConnectionPoolManager , HttpConnectionPool, HttpConnection, AuthenticationHelper.NtAuth and then back to HttpConnection to send the raw bytes.

Related

C# ASP.NET, testing websocket with Microsoft.AspNetCore.Mvc.Testing

I am currently writing an integration test for a websocket connection. I want to test a ClientWebSocket connection against a server instantiated by Microsoft.AspNetCore.Mvc.Testing. This does not seem to work. Does anybody have an idea how to get this to work?
My setup is as follows:
I have an API application, which offers some normal http endpoints and one websocket
I have some application code, which establishes a websocket connection
I have integration tests, which instantiate the API application using Microsoft.AspNetCore.Mvc.Testing and use my application code against it
I general am following https://learn.microsoft.com/de-de/aspnet/core/test/integration-tests?view=aspnetcore-6.0 . This works great for normal HTTP(S) endpoints. My Code looks like this:
_application = new WebApplicationFactory<Program>().WithWebHostBuilder(builder => { });
//HttpClient for http endpoints
_client = _application.CreateClient();
The WebSocket endpoint is created as described here: https://learn.microsoft.com/de-de/aspnet/core/fundamentals/websockets?view=aspnetcore-6.0
app.Use(async (context, next) =>
{
if (context.Request.Path == "/"+nameof(TestModel.WebsocketEvent))
{
if (context.WebSockets.IsWebSocketRequest)
{
TestModel.Instance.WebsocketEvent = await context.WebSockets.AcceptWebSocketAsync();
await ListenForClose(TestModel.Instance.WebsocketEvent);
}
}
}
For the connection to the endpoint I am using ClientWebsocket in the code I want to test.
var ws = new ClientWebSocket();
await ws.ConnectAsync(_href, cancellation);
When I run my API application manually and execute my code against that instance, everything works as expected. The WebSocket connection is established.
When I try to run it with Microsoft.AspNetCore.Mvc.Testing I get an exception that the server refused the connection.
To some degree it makes sense to me, since the ClientWebSocket is not using anything generated by the WebApplicationFactory.(e.g. _application.Server.CreateWebSocketClient.
On the other Hand I do want to use ClientWebSocket in my code and not inject the WebSocketClient of Microsoft.AspNetCore.Mvc.Testing into my production code. In Contrast to http where I actually get a regular HttpClient which I am fine with to inject.
Does anybody have an idea, how I can make the integration test working with ClientWebSocket?
Is there the possiblity to reuse the server Microsoft.AspNetCore.Mvc.Testing starts for requests other than what the WebApplicationFactory generates?
Is Microsoft.AspNetCore.Mvc.Testing running the API application at all as a server? Or is there some black magic in the background?
What I checked so far:
_href is correct
_application.Server.BaseAddress does not seem to have an impact
What I want to do is simply not possible. The answer of the following thread pushed me in the right direction.
Inject HttpClient from WebApplicationFactory
This matches the official documentation if you know what you are looking for:https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-6.0
Hence I can only access the instance of my Web API application using what WebApplicationFactory offers me.
I can't access the instance of the Web API application over my local network.
Thanks to everybody taking the time to read and think about my question.

AspNet Core 3 HttpClientFactory: TaskCanceledException

I'm using an aspnet core 3 reverse proxy in Azure Kubernetes Service, loosely based on ProxyKit, which used to work just fine in a previous cluster. On the new cluster (the only difference I can see is that the old one used kubenet, the new one an azure virtual network) I constantly get TaskCanceledExceptions on HttpClient.SendAsync when an upstream request takes more than a few seconds.
This is the relevant method that throws the exception:
return await _httpClient.SendAsync(
UpstreamRequest,
HttpCompletionOption.ResponseContentRead,
HttpContext.RequestAborted)
.ConfigureAwait(false);
The HttpClient is provided by HttpClientFactory using the typed AddHttpClient middleware.
Things i've tried so far:
explicitly set a 30 seconds timeout for the HttpClient
passing no CancelationToken to the SendAsync method
implement custom timeout handling as suggested in this article
This is how the HttpClientFactory was configured before:
var httpClientBuilder = services
.AddHttpClient<ProxyKitClient>()
.ConfigurePrimaryHttpMessageHandler(sp => new HttpClientHandler
{
AllowAutoRedirect = false,
UseCookies = false
});
And this is the configuration right now:
var httpClientBuilder = services
.AddHttpClient<ProxyKitClient>(o => o.Timeout = Timeout.InfiniteTimeSpan)
.ConfigurePrimaryHttpMessageHandler(sp => new TimeoutHandler
{
InnerHandler = new HttpClientHandler
{
AllowAutoRedirect = false,
UseCookies = false
}
});
The behavior did not change whatsoever.
How can i make sure that HttpClient waits for the upstream request to finish? Kestrel and HttpClient default timeouts are way higher than the requests that are aborted right now.
As a side note, when i revert to aspnet core 2.2, the behavior is exactly the same.
I commented:
Task cancellation for something like SendAsync is going to occur when the client closes the connection. As such, I think you're looking the wrong place. You need to figure out why the clients are closing the connection prematurely.
As a result, the OP was able to determine that this issue lied with Azure Application Gateway:
Makes sense, and you are totally right. Was pulling my remaining hair out the whole day and the culprit is Azure Application Gateway with a request timeout of 1 second.

Breeze.Sharp - Need ability to specify UseDefaultCredentials in DataService's HttpClient construction

As of 0.5.4 check-in, the Breeze.Sharp/DataService.cs::InitializeHttpClient method creates an HttpClient with the default constructor. My intranet breeze web api service has a .Net console application, which needs to be authenticated by the service using Windows authentication. In a pre-breeze universe, I would have created an HttpClient object that takes the following HttpClientHandler in the constructor, which would then send the user's credentials along with the http request:
HttpClientHandler handler = new HttpClientHandler()
{
UseDefaultCredentials = true
};
So the request here is to be able inject my own HttpClient into the DataService, or otherwise specify this setting when I construct an EntityManager.
For now, I have resolved the issue by modifying my own instance of the Breeze.Sharp library by updating the DataService constructor to optionally take in an HttpClientHandler, like so:
public DataService(String serviceName, HttpClientHandler handler = null) { ... }
I would prefer to keep using the main library so hopefully this issue gets tracked and resolved for everyone else. :)
Updated 6/3/2014
This is now supported in Breeze 0.5.5, available now
Previous post
This makes sense, I'll try to look into it before the new release. ... and thanks for the input.
Thanks Jay, I have integrated release 5.5 into my project and no longer need a private build. :)

What is the overhead of creating a new HttpClient per call in a WebAPI client?

What should be the HttpClient lifetime of a WebAPI client?
Is it better to have one instance of the HttpClient for multiple calls?
What's the overhead of creating and disposing a HttpClient per request, like in example below (taken from http://www.asp.net/web-api/overview/web-api-clients/calling-a-web-api-from-a-net-client):
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:9000/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// New code:
HttpResponseMessage response = await client.GetAsync("api/products/1");
if (response.IsSuccessStatusCode)
{
Product product = await response.Content.ReadAsAsync<Product>();
Console.WriteLine("{0}\t${1}\t{2}", product.Name, product.Price, product.Category);
}
}
HttpClient has been designed to be re-used for multiple calls. Even across multiple threads.
The HttpClientHandler has Credentials and Cookies that are intended to be re-used across calls. Having a new HttpClient instance requires re-setting up all of that stuff.
Also, the DefaultRequestHeaders property contains properties that are intended for multiple calls. Having to reset those values on each request defeats the point.
Another major benefit of HttpClient is the ability to add HttpMessageHandlers into the request/response pipeline to apply cross cutting concerns. These could be for logging, auditing, throttling, redirect handling, offline handling, capturing metrics. All sorts of different things. If a new HttpClient is created on each request, then all of these message handlers need to be setup on each request and somehow any application level state that is shared between requests for these handlers also needs to be provided.
The more you use the features of HttpClient, the more you will see that reusing an existing instance makes sense.
However, the biggest issue, in my opinion is that when a HttpClient class is disposed, it disposes HttpClientHandler, which then forcibly closes the TCP/IP connection in the pool of connections that is managed by ServicePointManager. This means that each request with a new HttpClient requires re-establishing a new TCP/IP connection.
From my tests, using plain HTTP on a LAN, the performance hit is fairly negligible. I suspect this is because there is an underlying TCP keepalive that is holding the connection open even when HttpClientHandler tries to close it.
On requests that go over the internet, I have seen a different story. I have seen a 40% performance hit due to having to re-open the request every time.
I suspect the hit on a HTTPS connection would be even worse.
My advice is to keep an instance of HttpClient for the lifetime of your application for each distinct API that you connect to.
If you want your application to scale, the difference is HUGE! Depending on the load, you will see very different performance numbers. As Darrel Miller mentions, the HttpClient was designed to be re-used across requests. This was confirmed by folks on the BCL team who wrote it.
A recent project I had was to help a very large and well-known online computer retailer scale out for Black Friday/holiday traffic for some new systems. We ran into some performance issues around the usage of HttpClient. Since it implements IDisposable, the devs did what you would normally do by creating an instance and placing it inside of a using() statement. Once we started load testing the app brought the server to its knees - yes, the server not just the app. The reason is that every instance of HttpClient opens a port on the server. Because of non-deterministic finalization of GC and the fact that you are working with computer resources that span across multiple OSI layers, closing network ports can take a while. In fact Windows OS itself can take up to 20 secs to close a port (per Microsoft). We were opening ports faster than they could be closed - server port exhaustion which hammered the CPU to 100%. My fix was to change the HttpClient to a static instance which solved the problem. Yes, it is a disposable resource, but any overhead is vastly outweighed by the difference in performance. I encourage you to do some load testing to see how your app behaves.
You can also check out the WebAPI Guidance page for documentation and example at
https://www.asp.net/web-api/overview/advanced/calling-a-web-api-from-a-net-client
Pay special attention to this call-out:
HttpClient is intended to be instantiated once and re-used throughout the life of an application. Especially in server applications, creating a new HttpClient instance for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors.
If you find that you need to use a static HttpClient with different headers, base address, etc. what you will need to do is to create the HttpRequestMessage manually and set those values on the HttpRequestMessage. Then, use the HttpClient:SendAsync(HttpRequestMessage requestMessage, ...)
UPDATE for .NET Core:
You should use the IHttpClientFactory via Dependency Injection to create HttpClient instances. It will manage the lifetime for you and you do not need to explicitly dispose it. See Make HTTP requests using IHttpClientFactory in ASP.NET Core
As the other answers state, HttpClient is meant for reuse. However, reusing a single HttpClient instance across a multi-threaded application means you can't change the values of its stateful properties, like BaseAddress and DefaultRequestHeaders (so you can only use them if they are constant across your application).
One approach for getting around this limitation is wrapping HttpClient with a class that duplicates all the HttpClient methods you need (GetAsync, PostAsync etc) and delegates them to a singleton HttpClient. However that's pretty tedious (you will need to wrap the extension methods too), and fortunately there is another way - keep creating new HttpClient instances, but reuse the underlying HttpClientHandler. Just make sure you don't dispose the handler:
HttpClientHandler _sharedHandler = new HttpClientHandler(); //never dispose this
HttpClient GetClient(string token)
{
//client code can dispose these HttpClient instances
return new HttpClient(_sharedHandler, disposeHandler: false)
{
DefaultRequestHeaders =
{
Authorization = new AuthenticationHeaderValue("Bearer", token)
}
};
}
Related to high-volume web sites but not directly to HttpClient. We have the snippet of code below in all of our services.
// number of milliseconds after which an active System.Net.ServicePoint connection is closed.
const int DefaultConnectionLeaseTimeout = 60000;
ServicePoint sp =
ServicePointManager.FindServicePoint(new Uri("http://<yourServiceUrlHere>"));
sp.ConnectionLeaseTimeout = DefaultConnectionLeaseTimeout;
From https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Net.ServicePoint.ConnectionLeaseTimeout);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5.2);k(DevLang-csharp)&rd=true
"You can use this property to ensure that a ServicePoint object's active connections do not remain open indefinitely. This property is intended for scenarios where connections should be dropped and reestablished periodically, such as load balancing scenarios.
By default, when KeepAlive is true for a request, the MaxIdleTime property sets the time-out for closing ServicePoint connections due to inactivity. If the ServicePoint has active connections, MaxIdleTime has no effect and the connections remain open indefinitely.
When the ConnectionLeaseTimeout property is set to a value other than -1, and after the specified time elapses, an active ServicePoint connection is closed after servicing a request by setting KeepAlive to false in that request.
Setting this value affects all connections managed by the ServicePoint object."
When you have services behind a CDN or other endpoint that you want to failover then this setting helps callers follow you to your new destination. In this example 60 seconds after a failover all callers should re-connect to the new endpoint. It does require that you know your dependent services (those services that YOU call) and their endpoints.
One thing to point out, that none of the "don't use using" blogs note is that it is not just the BaseAddress and DefaultHeader that you need to consider. Once you make HttpClient static, there are internal states that will be carried across requests. An example: You are authenticating to a 3rd party with HttpClient to get a FedAuth token (ignore why not using OAuth/OWIN/etc), that Response message has a Set-Cookie header for FedAuth, this is added to your HttpClient state. The next user to login to your API will be sending the last person's FedAuth cookie unless you are managing these cookies on each request.
You may also want to refer to this blog post by Simon Timms: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
But HttpClient is different. Although it implements the IDisposable interface it is actually a shared object. This means that under the covers it is reentrant) and thread safe. Instead of creating a new instance of HttpClient for each execution you should share a single instance of HttpClient for the entire lifetime of the application. Let’s look at why.
As a first issue, while this class is disposable, using it with the using statement is not the best choice because even when you dispose HttpClient object, the underlying socket is not immediately released and can cause a serious issue named ‘sockets exhaustion.
But there’s a second issue with HttpClient that you can have when you use it as singleton or static object. In this case, a singleton or static HttpClient doesn't respect DNS changes.
in .net core you can do the same with HttpClientFactory something like this:
public interface IBuyService
{
Task<Buy> GetBuyItems();
}
public class BuyService: IBuyService
{
private readonly HttpClient _httpClient;
public BuyService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<Buy> GetBuyItems()
{
var uri = "Uri";
var responseString = await _httpClient.GetStringAsync(uri);
var buy = JsonConvert.DeserializeObject<Buy>(responseString);
return buy;
}
}
ConfigureServices
services.AddHttpClient<IBuyService, BuyService>(client =>
{
client.BaseAddress = new Uri(Configuration["BaseUrl"]);
});
documentation and example at here

Using WCF service in MonoTouch with Authentication

I am using a WCF service client generated by slsvcutil form Silverlight toolkit version 4. I've also tried version 3 with the same problems. When I use a client instance running on http with no user credentials it runs without problems. But I need to switch to https for productive servers and send user credentials that are hardcoded for my application. I use the following code for that:
var binding = new BasicHttpBinding (BasicHttpSecurityMode.TransportCredentialOnly);
var endpoint = new EndpointAddress (AppSettings.FlareEndPoint);
_service = new TopicAnalystAPIClient(binding, endpoint);
_service.ClientCredentials.UserName.UserName = "xxx";
_service.ClientCredentials.UserName.Password = "xxx";
When I call a method on that service pointing to http with no authentication it works. When I use the this code against http/https with the credential I get "There was an error on processing web request: Status code 401(Unauthorized): Unauthorized" exception. I've checked that the credentials are correct, I am able to open the service reference in my browser. I've also tried several combinations of http/https and SecurityMode value. I've also tried it on four different servers always with the same result.
What can be the problem?
A lot of permutations are possible. BasicHttpSecurityMode.TransportCredentialOnly should be usable without SSL [1] using HTTP itself. This means the server will send one (or more) authentication method(s) to the client (e.g. basic, digest, ntlm) and Mono (including MonoTouch) should be providing support for the most of them.
It is possible that the linker (if used) removes one of them. In that case you could try building and testing without linking (or skip linking of System.Net.dll).
It's also possible that the authentication method that the serve insist on is not supported. You could find which one is used by running a network trace (e.g. wireshark) or, maybe, it will show up in more details in the server log (along with the 401 error).
[1] http://msdn.microsoft.com/en-us/library/system.servicemodel.basichttpsecuritymode%28v=vs.95%29.aspx

Categories