based on the article https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
// in startup.cs
builder.Services.AddHttpClient<ICatalogService, CatalogService>()
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
// usage
public class CatalogService : ICatalogService
{
private readonly HttpClient _httpClient;
private readonly string _remoteServiceBaseUrl;
public CatalogService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<Catalog> GetCatalogItems(int page, int take, int? brand, int? type)
{
var uri = API.Catalog.GetAllCatalogItems(_remoteServiceBaseUrl,
page, take, brand, type);
var responseString = await _httpClient.GetStringAsync(uri); // <------------------
var catalog = JsonConvert.DeserializeObject<Catalog>(responseString);
return catalog;
}
}
can I say after await _httpClient.GetStringAsync(uri)
:
A. The underlying TCP connection is not closed, no FIN
packet has been sent to the server
B. After 5mins, the TCP connection will be closed, so FIN
packet is sent to the server, but still there will be around 4 mins TIME_Wait period for it (twice the maximum segment lifetime which is 2mins by default), so the TCP connectio is actually closed after 9 mins?
is my understanding on A and B correct?
If you want to have more control about the underlying sockets' lifecycle then I would suggest to specify the PooledConnectionIdleTimeout
and/or PooledConnectionLifetime
of the SocketsHttpHandler
:
var handler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(...),
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(...)
};
var client = new HttpClient(handler);
UPDATE #1
is my understanding on A and B correct?
Using a long-living HttpClient
is different then using HttpClientFactory
with short-living HttpMessageHandler
s.
Your question is about the second case so, lets stick with that.
If you don't specify the HandlerLifetime
then it will use 2 minutes as a default value. If you set it explicitly like you did via the SetHandlerLifetime
then it will use that TimeSpan
as the activity tracking period.
Inside the DefaultHttpClientFactory
there is a method called CreateHandlerEntry
which will call the following method:
return new ActiveHandlerTrackingEntry(name, handler, scope, options.HandlerLifetime);
The ActiveHandlerTrackingEntry
will use a simple timer to track the elapsed period without activity. It does not bother the handler directly.
Rather it allows to the callers of the StartExpiryTimer
method to specify a TimerCallback
. The DefaultHttpClientFactory
passes the ExpiryTimer_Tick
delegate.
This method simply removes the handler from the active handlers and moves that into the expired handlers queue (there might be outstanding/pending requests still).
var active = (ActiveHandlerTrackingEntry)state!;
bool removed = _activeHandlers.TryRemove(active.Name, out Lazy<ActiveHandlerTrackingEntry>? found);
...
var expired = new ExpiredHandlerTrackingEntry(active);
_expiredHandlers.Enqueue(expired);
The CleanupTimer_Tick
will take care of the expired handlers. It will Dispose
the ActiveHandlerTrackingEntry
's InnerHandler
, which is a marker class called LifetimeTrackingHttpMessageHandler
.
Gladly that lengthy named handler receives the actual message handler as its constructor parameter. That received message handler is generated via a HttpMessageHandlerBuilder
class. In case of the DefaultHttpClientFactory
this will be the DefaultHttpMessageHandlerBuilder
. That class uses the HttpClientHandler
not the SocketsHttpHandler
as its primary message handler.
Depending on the TARGET_BROWSER
the underlying handler will be either the BrowserHttpHandler
or the SocketsHttpHandler
#if TARGET_BROWSER
using System.Diagnostics;
using System.Net.Http.Metrics;
using HttpHandlerType = System.Net.Http.BrowserHttpHandler;
#else
using HttpHandlerType = System.Net.Http.SocketsHttpHandler;
#endif
...
My point here (as it was said by others in the comments section) is that the HttpClientFactory
is at a much higher abstraction level than the actual TCP messages. If you search for FIN
or TIME_WAIT
messages then you will only find the tcp_fsm.h
.
My advice here is that if you really care about the TCP messages then use TCPView to scrunatize the underlying communication. It might require several experiments to see how certain method calls changes the underlying connection lifecycle.