zoukankan      html  css  js  c++  java
  • 重新整理 .net core 实践篇—————HttpClientFactory[三十二]

    前言

    简单整理一下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。

  • 相关阅读:
    Linux 中如何用源代码安装软件,以及如何卸载它
    Linux 中的 Install命令
    PHP 常用header头定义
    如何防止重复提交表单?
    如何从二维数组中的多个key中获取指定key的值?
    jquery的ajax全局事件详解
    PHP+MySQL分页显示示例分析
    javascript中的事件类型
    事件委托/事件代理,
    彻底弄懂JS的事件冒泡和事件捕获
  • 原文地址:https://www.cnblogs.com/aoximin/p/14940319.html
Copyright © 2011-2022 走看看