重新整理 .net core 實踐篇—————HttpClientFactory[三十二]

不問前世發表於2021-06-28

前言

簡單整理一下HttpClientFactory 。

正文

這個HttpFactory 主要有下面的功能:

  1. 管理內部HttpMessageHandler 的生命週期,靈活應對資源問題和DNS重新整理問題

  2. 支援命名話、型別化配置,集中管理配置,避免衝突。

  3. 靈活的出站請求管道配置,輕鬆管理請求生命週期

  4. 內建管道最外層和最內層日誌記錄器,有information 和 Trace 輸出

核心物件:

  1. HttpClient

  2. HttpMessageHandler

  3. SocketsHttpHandler

  4. DelegatingHandler

  5. IHttpClientFactory

  6. IHttpClientBuilder

請求的大概流程圖為:

從上圖中看到SocketsHttpHandler 才是正確去發起請求。

裡面的logginScopeHttpMessageHandler 、CustomMessageHandler 還有LoggingHttpMessageHandler,他們都是中間處理,處於管道之中,可以理解為中介軟體部分。

logginScopeHttpMessageHandler 是沒有經過CustomMessageHandler 的日誌,LoggingHttpMessageHandler 是經過CustomMessageHandler 的日誌,也就是說LoggingHttpMessageHandler 才是正在去傳送請求的資料。

HttpClientFactory 提供了三種建立HttpClient的模式:

建立模式:

  1. 工廠模式

  2. 命名客戶端模式

  3. 型別化客戶端模式

那麼就來看一下各種模式的不同吧。

工廠模式

在.net core 中使用工廠模式,要引入:

services.AddHttpClient();

然後使用:

public class OrderServiceClient
{
	private IHttpClientFactory _httpClientFactory;

	public OrderServiceClient(IHttpClientFactory httpClientFactory)
	{
		_httpClientFactory = httpClientFactory;
	}

	public async Task<string> Get()
	{
		var client = _httpClientFactory.CreateClient();

		return await client.GetStringAsync("http://localhost:9000/WeatherForecast");
	}
}

這樣就是使用工廠模式了。

可能有些人認為IHttpClientFactory,可能它的實現是HttpClientFactory,因為.net core的基礎庫中就有HttpClientFactory,然而實際不是,那麼來看下原始碼。

public static IServiceCollection AddHttpClient(
  this IServiceCollection services)
{
  if (services == null)
	throw new ArgumentNullException(nameof (services));
  services.AddLogging();
  services.AddOptions();
  services.TryAddTransient<HttpMessageHandlerBuilder, DefaultHttpMessageHandlerBuilder>();
  services.TryAddSingleton<DefaultHttpClientFactory>();
  services.TryAddSingleton<IHttpClientFactory>((Func<IServiceProvider, IHttpClientFactory>) (serviceProvider => (IHttpClientFactory) serviceProvider.GetRequiredService<DefaultHttpClientFactory>()));
  services.TryAddSingleton<IHttpMessageHandlerFactory>((Func<IServiceProvider, IHttpMessageHandlerFactory>) (serviceProvider => (IHttpMessageHandlerFactory) serviceProvider.GetRequiredService<DefaultHttpClientFactory>()));
  services.TryAdd(ServiceDescriptor.Transient(typeof (ITypedHttpClientFactory<>), typeof (DefaultTypedHttpClientFactory<>)));
  services.TryAdd(ServiceDescriptor.Singleton(typeof (DefaultTypedHttpClientFactory<>.Cache), typeof (DefaultTypedHttpClientFactory<>.Cache)));
  services.TryAddEnumerable(ServiceDescriptor.Singleton<IHttpMessageHandlerBuilderFilter, LoggingHttpMessageHandlerBuilderFilter>());
  services.TryAddSingleton<HttpClientMappingRegistry>(new HttpClientMappingRegistry());
  return services;
}

其他暫且不看,IHttpClientFactory的實現類是DefaultHttpClientFactory;

看下CreateClient:

public HttpClient CreateClient(string name)
{
  if (name == null)
	throw new ArgumentNullException(nameof (name));
  HttpClient httpClient = new HttpClient(this.CreateHandler(name), false);
  HttpClientFactoryOptions clientFactoryOptions = this._optionsMonitor.Get(name);
  for (int index = 0; index < clientFactoryOptions.HttpClientActions.Count; ++index)
	clientFactoryOptions.HttpClientActions[index](httpClient);
  return httpClient;
}

public HttpMessageHandler CreateHandler(string name)
{
  if (name == null)
	throw new ArgumentNullException(nameof (name));
  ActiveHandlerTrackingEntry entry = this._activeHandlers.GetOrAdd(name, this._entryFactory).Value;
  this.StartHandlerEntryTimer(entry);
  return (HttpMessageHandler) entry.Handler;
}

這裡我們似乎沒有傳遞name,那麼有可能是擴充套件類:

public static HttpClient CreateClient(this IHttpClientFactory factory)
{
  if (factory == null)
	throw new ArgumentNullException(nameof (factory));
  return factory.CreateClient(Microsoft.Extensions.Options.Options.DefaultName);
}

測試一下:

注入服務:

 services.AddSingleton<OrderServiceClient>();

測試程式碼:

[Route("api/[Controller]")]
public class OrderController : Controller
{
	OrderServiceClient _orderServiceClient;

	public OrderController(OrderServiceClient orderServiceClient)
	{
		_orderServiceClient = orderServiceClient;
	}

	[HttpGet("Get")]
	public async Task<string> Get()
	{
		return await _orderServiceClient.Get();
	}
}

結果:

這裡說明一下,那個訪問9000的埠,就是新建一個api專案,然後把埠改成9000的demo,這裡就不演示了。

命令客戶端方式

services.AddHttpClient();
services.AddHttpClient("NamedOrderServiceClient", client =>
{
	client.DefaultRequestHeaders.Add("token","123456");
	client.BaseAddress = new Uri("http://locahost:9000");
});

前面提及到client可以命名,那麼這裡就可以提前建立好對應的客戶端配置。
AddHttpClient:

public static IHttpClientBuilder AddHttpClient(
  this IServiceCollection services,
  string name,
  Action<HttpClient> configureClient)
{
  if (services == null)
	throw new ArgumentNullException(nameof (services));
  if (name == null)
	throw new ArgumentNullException(nameof (name));
  if (configureClient == null)
	throw new ArgumentNullException(nameof (configureClient));
  services.AddHttpClient();
  DefaultHttpClientBuilder builder = new DefaultHttpClientBuilder(services, name);
  builder.ConfigureHttpClient(configureClient);
  return (IHttpClientBuilder) builder;
}

看下這個ConfigureHttpClient:

public static IHttpClientBuilder ConfigureHttpClient(
  this IHttpClientBuilder builder,
  Action<HttpClient> configureClient)
{
  if (builder == null)
	throw new ArgumentNullException(nameof (builder));
  if (configureClient == null)
	throw new ArgumentNullException(nameof (configureClient));
  builder.Services.Configure<HttpClientFactoryOptions>(builder.Name, (Action<HttpClientFactoryOptions>) (options => options.HttpClientActions.Add(configureClient)));
  return builder;
}

可以看到這裡只是做了配置,其他什麼也沒幹。那麼什麼時候用到的呢?

public HttpClient CreateClient(string name)
{
  if (name == null)
	throw new ArgumentNullException(nameof (name));
  HttpClient httpClient = new HttpClient(this.CreateHandler(name), false);
  HttpClientFactoryOptions clientFactoryOptions = this._optionsMonitor.Get(name);
  for (int index = 0; index < clientFactoryOptions.HttpClientActions.Count; ++index)
	clientFactoryOptions.HttpClientActions[index](httpClient);
  return httpClient;
}

HttpClientFactoryOptions 眼熟吧。

client =>{
       client.DefaultRequestHeaders.Add("token","123456");
       client.BaseAddress = new Uri("http://localhost:9000");
}

然後clientFactoryOptions.HttpClientActionsindex;就會呼叫上面的這個Action。

那麼我們使用的時候這麼寫:

public class NamedOrderServiceClient
{
	private IHttpClientFactory _httpClientFactory;

	private const string _clientName = "NamedOrderServiceClient";

	public NamedOrderServiceClient(IHttpClientFactory httpClientFactory)
	{
		_httpClientFactory = httpClientFactory;
	}

	public async Task<string> Get()
	{
		var client = _httpClientFactory.CreateClient(_clientName);

		return await client.GetStringAsync("/WeatherForecast");
	}
}

測試一下:
注入服務:

services.AddSingleton<NamedOrderServiceClient>();

測試程式碼:

[Route("api/[Controller]")]
public class OrderController : Controller
{
	NamedOrderServiceClient _orderServiceClient;

	public OrderController(NamedOrderServiceClient orderServiceClient)
	{
		_orderServiceClient = orderServiceClient;
	}

	[HttpGet("Get")]
	public async Task<string> Get()
	{
		return await _orderServiceClient.Get();
	}
}

斷點一下:

可以看到建立的httpclient 屬性如上。

效果如下:

其實在我們使用過程中最好去使用這種方式,有兩個好處。

  1. 不同客戶端可以單獨配置

  2. 不同的可以的生命週期不同,即使一個httpclient崩潰了,另外一個httpclient也可以正常請求。

那麼除了配置client的一些基本配置,如baseurl或者header這種。

services.AddHttpClient("NamedOrderServiceClient", client =>
{
	client.DefaultRequestHeaders.Add("token","123456");
	client.BaseAddress = new Uri("http://localhost:9000");
}).SetHandlerLifetime(TimeSpan.FromMinutes(20));

還可以設定dns時間。

當然最重要可以為每個httpclient自定義不同的管道,上文提及到到達正在的執行的過程中,會經過管道,中間我們可以自定義。

public class RequestCustomHandler: DelegatingHandler
{
	protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken)
	{
		requestMessage.Headers.Add("token2",Guid.NewGuid().ToString());
                // 請求前處理
		var request = await base.SendAsync(requestMessage, cancellationToken);
                // 請求後處理
		return request;
	}
}

然後這樣加入:

 services.AddSingleton<RequestCustomHandler>();
services.AddHttpClient("NamedOrderServiceClient", client =>
{
	client.DefaultRequestHeaders.Add("token","123456");
	client.BaseAddress = new Uri("http://localhost:9000");
}).SetHandlerLifetime(TimeSpan.FromMinutes(20)).AddHttpMessageHandler(provider=>provider.GetService<RequestCustomHandler>());

那麼來看下其管道怎麼實現的:

    public static IHttpClientBuilder AddHttpMessageHandler(
      this IHttpClientBuilder builder,
      Func<IServiceProvider, DelegatingHandler> configureHandler)
    {
      if (builder == null)
        throw new ArgumentNullException(nameof (builder));
      if (configureHandler == null)
        throw new ArgumentNullException(nameof (configureHandler));
      builder.Services.Configure<HttpClientFactoryOptions>(builder.Name, (Action<HttpClientFactoryOptions>) (options => options.HttpMessageHandlerBuilderActions.Add((Action<HttpMessageHandlerBuilder>) (b => b.AdditionalHandlers.Add(configureHandler(b.Services))))));
      return builder;
    }

其也就是做了一些配置,生成了一些action,那麼哪裡呼叫了呢?

在DefaultHttpClientFactory:

public DefaultHttpClientFactory(
  IServiceProvider services,
  IServiceScopeFactory scopeFactory,
  ILoggerFactory loggerFactory,
  IOptionsMonitor<HttpClientFactoryOptions> optionsMonitor,
  IEnumerable<IHttpMessageHandlerBuilderFilter> filters)
{
  if (services == null)
	throw new ArgumentNullException(nameof (services));
  if (scopeFactory == null)
	throw new ArgumentNullException(nameof (scopeFactory));
  if (loggerFactory == null)
	throw new ArgumentNullException(nameof (loggerFactory));
  if (optionsMonitor == null)
	throw new ArgumentNullException(nameof (optionsMonitor));
  if (filters == null)
	throw new ArgumentNullException(nameof (filters));
  this._services = services;
  this._scopeFactory = scopeFactory;
  this._optionsMonitor = optionsMonitor;
  this._filters = filters.ToArray<IHttpMessageHandlerBuilderFilter>();
  this._logger = (ILogger) loggerFactory.CreateLogger<DefaultHttpClientFactory>();
  this._activeHandlers = new ConcurrentDictionary<string, Lazy<ActiveHandlerTrackingEntry>>((IEqualityComparer<string>) StringComparer.Ordinal);
  this._entryFactory = (Func<string, Lazy<ActiveHandlerTrackingEntry>>) (name => new Lazy<ActiveHandlerTrackingEntry>((Func<ActiveHandlerTrackingEntry>) (() => this.CreateHandlerEntry(name)), LazyThreadSafetyMode.ExecutionAndPublication));
  this._expiredHandlers = new ConcurrentQueue<ExpiredHandlerTrackingEntry>();
  this._expiryCallback = new TimerCallback(this.ExpiryTimer_Tick);
  this._cleanupTimerLock = new object();
  this._cleanupActiveLock = new object();
}

看到這一行:

 this._entryFactory = (Func<string, Lazy<ActiveHandlerTrackingEntry>>) (name => new Lazy<ActiveHandlerTrackingEntry>((Func<ActiveHandlerTrackingEntry>) (() => this.CreateHandlerEntry(name)), LazyThreadSafetyMode.ExecutionAndPublication));

關注一下這個CreateHandlerEntry,這裡呼叫了,後面會看到。

當我們建立client的時候:

public HttpClient CreateClient(string name)
{
  if (name == null)
	throw new ArgumentNullException(nameof (name));
  HttpClient httpClient = new HttpClient(this.CreateHandler(name), false);
  HttpClientFactoryOptions clientFactoryOptions = this._optionsMonitor.Get(name);
  for (int index = 0; index < clientFactoryOptions.HttpClientActions.Count; ++index)
	clientFactoryOptions.HttpClientActions[index](httpClient);
  return httpClient;
}

public HttpMessageHandler CreateHandler(string name)
{
  if (name == null)
	throw new ArgumentNullException(nameof (name));
  ActiveHandlerTrackingEntry entry = this._activeHandlers.GetOrAdd(name, this._entryFactory).Value;
  this.StartHandlerEntryTimer(entry);
  return (HttpMessageHandler) entry.Handler;
}

在CreateHandler 呼叫_entryFactory:

那麼來看一下CreateHandlerEntry:

internal ActiveHandlerTrackingEntry CreateHandlerEntry(string name)
{
  IServiceProvider provider = this._services;
  IServiceScope scope = (IServiceScope) null;
  HttpClientFactoryOptions options = this._optionsMonitor.Get(name);
  if (!options.SuppressHandlerScope)
  {
	scope = this._scopeFactory.CreateScope();
	provider = scope.ServiceProvider;
  }
  try
  {
	HttpMessageHandlerBuilder requiredService = provider.GetRequiredService<HttpMessageHandlerBuilder>();
	requiredService.Name = name;
	Action<HttpMessageHandlerBuilder> next = new Action<HttpMessageHandlerBuilder>(Configure);
	for (int index = this._filters.Length - 1; index >= 0; --index)
	  next = this._filters[index].Configure(next);
	next(requiredService);
	LifetimeTrackingHttpMessageHandler handler = new LifetimeTrackingHttpMessageHandler(requiredService.Build());
	return new ActiveHandlerTrackingEntry(name, handler, scope, options.HandlerLifetime);
  }
  catch
  {
	scope?.Dispose();
	throw;
  }

  void Configure(HttpMessageHandlerBuilder b)
  {
	for (int index = 0; index < options.HttpMessageHandlerBuilderActions.Count; ++index)
	  options.HttpMessageHandlerBuilderActions[index](b);
  }
}

在Configure 執行了我們前面執行的action,也就是b => b.AdditionalHandlers.Add(configureHandler(b.Services))。

那麼這個AdditionalHandlers有啥用?

這裡簡單說一下哈,詳細會到細節篇中說明。

httpclient 執行請求,其實最後是HttpMessageHandler去執行。那麼這個HttpMessageHandler 怎麼來的呢?

public abstract class HttpMessageHandlerBuilder
{
public abstract string Name { get; set; }

public abstract HttpMessageHandler PrimaryHandler { get; set; }

public abstract IList<DelegatingHandler> AdditionalHandlers { get; }

public virtual IServiceProvider Services { get; }

public abstract HttpMessageHandler Build();

protected internal static HttpMessageHandler CreateHandlerPipeline(
  HttpMessageHandler primaryHandler,
  IEnumerable<DelegatingHandler> additionalHandlers)
{
  if (primaryHandler == null)
	throw new ArgumentNullException(nameof (primaryHandler));
  if (additionalHandlers == null)
	throw new ArgumentNullException(nameof (additionalHandlers));
  IReadOnlyList<DelegatingHandler> delegatingHandlerList = (IReadOnlyList<DelegatingHandler>) ((object) (additionalHandlers as IReadOnlyList<DelegatingHandler>) ?? (object) additionalHandlers.ToArray<DelegatingHandler>());
  HttpMessageHandler httpMessageHandler = primaryHandler;
  for (int index = delegatingHandlerList.Count - 1; index >= 0; --index)
  {
	DelegatingHandler delegatingHandler = delegatingHandlerList[index];
	if (delegatingHandler == null)
	  throw new InvalidOperationException(Resources.FormatHttpMessageHandlerBuilder_AdditionalHandlerIsNull((object) nameof (additionalHandlers)));
	if (delegatingHandler.InnerHandler != null)
	  throw new InvalidOperationException(Resources.FormatHttpMessageHandlerBuilder_AdditionHandlerIsInvalid((object) "InnerHandler", (object) "DelegatingHandler", (object) nameof (HttpMessageHandlerBuilder), (object) Environment.NewLine, (object) delegatingHandler));
	delegatingHandler.InnerHandler = httpMessageHandler;
	httpMessageHandler = (HttpMessageHandler) delegatingHandler;
  }
  return httpMessageHandler;
}
}

AdditionalHandlers 眼熟吧,這個HttpMessageHandler 和中介軟體一樣玩的都是套娃功能,形成一個小週天。

型別化客戶端模式

這個是什麼呢?

public class TypeOrderServiceClient
{
	private HttpClient _httpClient;

	public TypeOrderServiceClient(HttpClient httpClientFactory)
	{
		_httpClient = httpClientFactory;
	}

	public async Task<string> Get()
	{

		return await _httpClient.GetStringAsync("http://localhost:9000/WeatherForecast");
	}
}

這種最為簡單,直接生成了HttpClient ,名字就是TypeOrderServiceClient。

那麼我們是否能夠為其新增一些配置呢?可以的。

services.AddHttpClient<TypeOrderServiceClient>( client =>
{
	client.DefaultRequestHeaders.Add("token","123456");
	client.BaseAddress = new Uri("http://localhost:9000");
}).SetHandlerLifetime(TimeSpan.FromMinutes(20)).AddHttpMessageHandler(provider=>provider.GetService<RequestCustomHandler>());

這樣就ok的,沒有名字會使用泛型名。

public static IHttpClientBuilder AddHttpClient<TClient>(
  this IServiceCollection services,
  Action<HttpClient> configureClient)
  where TClient : class
{
  if (services == null)
	throw new ArgumentNullException(nameof (services));
  if (configureClient == null)
	throw new ArgumentNullException(nameof (configureClient));
  services.AddHttpClient();
  string typeDisplayName = TypeNameHelper.GetTypeDisplayName(typeof (TClient), false, false, true, '+');
  DefaultHttpClientBuilder builder = new DefaultHttpClientBuilder(services, typeDisplayName);
  builder.ConfigureHttpClient(configureClient);
  builder.AddTypedClientCore<TClient>(true);
  return (IHttpClientBuilder) builder;
}

以上只是個人整理,如有錯誤,望請指點。下一節grpc。

相關文章