Written in front

When creating an HttpClient instance, the HttpMessageHandler chain will be created internally. We know that HttpMessageHandler is the abstract handler responsible for establishing the connection. Therefore, the maintenance of HttpClient is actually to maintain the use of HttpMessageHandler. The release of HttpClient will not release the connection in time. It is common to create a globally used HttpClient instance to reduce the number of repeated connections. Of course, the drawbacks brought by this method are also obvious. Because the current HttpClient instance points to a problem with the server or the DNS changes, the instance cannot be automatically updated.
The following is the flow chart for running it:


HttpClientFactory introduced from .NET Core 2.1, it can be considered as a centralization of configuration and creation of HttpClient. The .NET Core introduces HttpClientFactory to automatically maintain the HttpMessageHandler pool and its life cycle, avoiding the common DNS that occurs when manually managing the HttpClient lifetime. problem. By default, the MessageHandler is active for two minutes, which means that after two minutes, the HttpClient instance can be relocated to the correct host.
The discussion of this article will start from the code we can see.

Detailed introduction

The functionality of HttpClientFactory is primarily in the Microsoft.Extensions.Http package, which is included by default in the Microsoft.AspNetCore.App meta package. For processing HttpClientFactory involves IHttpClientBuilder, IHttpClientFactory, IHttpMessageHandlerFactory, ITypedHttpClientFactory several major interface will be discussed separately.


When we create or configure the HttpClient object, we will ConfigureServicesadd services.AddHttpClient() to the method to register the IHttpClientFactory.

This code is located in Microsoft.Extensions.DependencyInjection.HttpClientFactoryServiceCollectionExtensions, which initializes the relevant information and registers it with the IServiceCollection, which includes logs, options, core abstraction functions, type clients, and other infrastructure features.

It should be noted that in the core abstract function, DefaultHttpClientFactory is a singleton mode, and the acquisition of the interface object it inherits is also a singleton. The HttpMessageHandlerBuilder registration method is to create a new HttpMessageHandlerBuilder instance every time GetService.

The following is the source code for services.AddHttpClient(), where the red part is the registration of the core abstraction function:

   1:   public  static IServiceCollection AddHttpClient( this IServiceCollection services)
   2:   {
   3:       if (services == null )
   4:       {
   5:           throw  new ArgumentNullException(nameof(services));
   6:       }
   8:       services.AddLogging();
   9:       services.AddOptions();

11: services.TryAddTransient<HttpMessageHandlerBuilder, DefaultHttpMessageHandlerBuilder>(); 12: services.AddSingleton<DefaultHttpClientFactory>(); 13: services.TryAddSingleton<IHttpClientFactory>(serviceProvider => serviceProvider.GetRequiredService<DefaultHttpClientFactory>()); 14: services. TryAddSingleton<IHttpMessageHandlerFactory>(serviceProvider => serviceProvider.GetRequiredService<DefaultHttpClientFactory>());

  16:       services.TryAdd(ServiceDescriptor.Transient( typeof (ITypedHttpClientFactory<>), typeof (DefaultTypedHttpClientFactory<>)));
  17:       services.TryAdd(ServiceDescriptor.Transient( typeof (DefaultTypedHttpClientFactory<>.Cache), typeof (DefaultTypedHttpClientFactory<>.Cache)));
  19:       services.TryAddEnumerable(ServiceDescriptor.Singleton<IHttpMessageHandlerBuilderFilter, LoggingHttpMessageHandlerBuilderFilter>());
  21:       return services;
  22:   }


DefaultHttpClientFactory is a class decorated with internal, meaning that the class can only be used internally. It inherits the two interfaces IHttpClientFactory and IHttpMessageHandlerFactory. Thus, the creation of the DefaultHttpClientFactory instance has been split into two behaviors.

The location of IHttpClientFactory is an abstract factory that can create a custom configuration for an HttpClient instance of the specified name. It has only one method, HttpClient CreateClient(string name).

The location of the IHttpMessageHandlerFactory is also an abstract factory that creates a custom configuration for the HttpMessageHandler instance of the specified name. It has only one method, HttpMessageHandler CreateHandler(string name).

Let’s take a look at the implementation of these two methods, I will find it very interesting.

   1:   public HttpClient CreateClient( string name)
   2:   {
   3:       if (name == null )
   4:       {
   5:           throw  new ArgumentNullException(nameof(name));
   6:       }
   8:       var handler = CreateHandler(name);
   9:       var client = new HttpClient(handler, disposeHandler: false );
  11:       var options = _optionsMonitor.Get(name);
  12:       for (var i = 0; i < options.HttpClientActions.Count; i++)
  13:       {
  14:           options.HttpClientActions[i](client);
  15:       }
  17:       return client;
  18:   }
  20:   public HttpMessageHandler CreateHandler( string name)
  21:   {
  22:       if (name == null )
  23:       {
  24:           throw  new ArgumentNullException(nameof(name));
  25:       }
  27:       var entry = _activeHandlers.GetOrAdd(name, _entryFactory).Value;
  29:       StartHandlerEntryTimer(entry);
  31:       return entry.Handler;
  32:   }

It can be seen that when we look up the HttpClient object by name, we also use the GetOrAdd method to find the corresponding HttpMessageHandler object according to the name, which means that the HttpClient object and the HttpMessageHandler object can be associated by name.

Need to pay attention to when calling the CreateHandler method will call the StartHandlerEntryTimer method, what is the method, he maintains the timer. This method is located in Microsoft.Extensions.Http.ActiveHandlerTrackingEntry, we treat this class as immutable (of course, its internal timers are changed), creating a “expired” pool that can significantly simplify threading requirements. New object.

In addition to these two methods, we need to pay attention to the management function of DefaultHttpClientFactory on HttpMessageHandler. The DefaultHttpClientFactory internal maintainer has a timer and a collection of two HttpMessageHandler objects, which are ActiveHandler and ExpiredHandler, respectively. The internal timer periodically scans and cleans up the invalid HttpMessageHandler object from the ExpiredHandler collection.

The increase in the ActiveHandler collection is added when the CreateHandler method is called. The removal is removed during the callback, and this removal entry is only the one.

The addition of the ExpiredHandler collection is also added by an internal callback mechanism when the CreateHandler method is called, and its removal is implemented by periodic scans of the timer. The important thing to note here is that there is an attribute in the ExpiredHandlerTrackingEntry class. The code is as follows:

   1:   private  readonly WeakReference _livenessTracker;
   1:   public  bool CanDispose => !_livenessTracker.IsAlive;

The variable of type WeakReference is used to identify whether the HttpMessageHandler object should be removed from the collection.

The timer is generally a relatively consuming resource, and once it is not used properly, it will cause a thread problem. When the DefaultHttpClientFactory processes the timer, it first stops all pending timers. If it needs to continue processing the invalid HttpMessageHandler object after clearing, The timer will be restarted, although it may seem redundant, but it is much better than locking the entire cleanup mechanism to determine if it is blocking the cleanup and starting the timer.

   1:   internal  void CleanupTimer_Tick()
   2:   {
   3:       StopCleanupTimer();
   5:       if (!Monitor.TryEnter(_cleanupActiveLock))
   6:       {
   7:           StartCleanupTimer();
   8:           return ;
   9:       }
  11:       try
  12:       {
  13:           var initialCount = _expiredHandlers.Count;
  14:           Log.CleanupCycleStart(_logger, initialCount);
  18:           var disposedCount = 0;
  19:           //Start cleaning up
  21:           Log.CleanupCycleEnd(_logger, stopwatch.GetElapsedTime(), disposedCount, _expiredHandlers.Count);
  22:       }
  23:       finally
  24:       {
  25:           Monitor.Exit(_cleanupActiveLock);
  26:       }
  28:       if (_expiredHandlers.Count > 0)
  29:       {
  30:           StartCleanupTimer();
  31:       }
  32:   }

The following is a schematic diagram of the processing of these two queues:



Orignal link:https://www.cnblogs.com/edison0621/p/11224890.html