上篇文章.NET Core HttpClient+Consul实现服务发现提到过,HttpClient存在套接字延迟释放的问题,高并发状况致使端口号被耗尽引发服务器拒绝服务的问题。好在微软意识到了这个问题,从.NET Core 2.1版本开始推出了HttpClientFactory来弥补这个问题。关于更详细的HttpClientFactory介绍能够查看微软官方文档 https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.1#httpclient-and-lifetime-management 咱们了解到想把自定义的HttpMessageHandler注入到HttpClient内部,必需要经过构造函数。接下来咱们就慢慢发觉如何给HttpClientFactory使用咱们自定义的Handler。html
相信你们都已经清楚使用HttpClientFactory从services.AddHttpClient()注入相关类开始,咱们就从这里开始入手。先贴上源码地址HttpClientFactoryServiceCollectionExtensions源码而后咱们大概的看一下咱们关注的实现方法,大体以下,代码有删减git
/// <summary> /// Adds the <see cref="IHttpClientFactory"/> and related services to the <see cref="IServiceCollection"/>. /// </summary> /// <param name="services">The <see cref="IServiceCollection"/>.</param> /// <returns>The <see cref="IServiceCollection"/>.</returns> public static IServiceCollection AddHttpClient(this IServiceCollection services) { if (services == null) { throw new ArgumentNullException(nameof(services)); } ..... // // Core abstractions // services.TryAddTransient<HttpMessageHandlerBuilder, DefaultHttpMessageHandlerBuilder>(); services.TryAddSingleton<DefaultHttpClientFactory>(); services.TryAddSingleton<IHttpClientFactory>(serviceProvider => serviceProvider.GetRequiredService<DefaultHttpClientFactory>()); services.TryAddSingleton<IHttpMessageHandlerFactory>(serviceProvider => serviceProvider.GetRequiredService<DefaultHttpClientFactory>()); ..... return services; }
经过源码咱们能够看到IHttpClientFactory的实现类注入实际上是DefaultHttpClientFactory,拿咱们继续顺着源码继续查找DefaultHttpClientFactory源码地址找到了咱们熟悉的名字😄😄😄github
public HttpClient CreateClient(string name) { if (name == null) { throw new ArgumentNullException(nameof(name)); } var handler = CreateHandler(name); var client = new HttpClient(handler, disposeHandler: false); var options = _optionsMonitor.Get(name); for (var i = 0; i < options.HttpClientActions.Count; i++) { options.HttpClientActions[i](client); } return client; }
在这里咱们发现了CreateHandler方法由它建立了handler传入了HttpClient,继续向下看,发现这段代码算法
public HttpMessageHandler CreateHandler(string name) { if (name == null) { throw new ArgumentNullException(nameof(name)); } var entry = _activeHandlers.GetOrAdd(name, _entryFactory).Value; StartHandlerEntryTimer(entry); return entry.Handler; }
而后咱们_entryFactory这个委托,而后一直找啊找服务器
internal ActiveHandlerTrackingEntry CreateHandlerEntry(string name) { ..... try { var builder = services.GetRequiredService<HttpMessageHandlerBuilder>(); builder.Name = name; Action<HttpMessageHandlerBuilder> configure = Configure; for (var i = _filters.Length - 1; i >= 0; i--) { configure = _filters[i].Configure(configure); } configure(builder); var handler = new LifetimeTrackingHttpMessageHandler(builder.Build()); return new ActiveHandlerTrackingEntry(name, handler, scope, options.HandlerLifetime); ..... } catch { ..... } }
发现了HttpMessageHandlerBuilder的身影,由它构建了HttpMessageHandler,咦!好像在哪见过,恍然大悟原来是在AddHttpClient扩展方法里并发
services.TryAddTransient<HttpMessageHandlerBuilder, DefaultHttpMessageHandlerBuilder>();
而后找到了DefaultHttpMessageHandlerBuilder在这里我看到了熟悉的身影
找到这里心里一阵澎湃,也就是说只要把我实现的HttpClientHandler替换掉默认的就行了,但是感受无地方下手啊。这时忽然想到DefaultHttpMessageHandlerBuilder这类是注册进来的,那我本身实现一个ConsulHttpMessageHandlerBuilder替换掉默认注册的DefaultHttpMessageHandlerBuilder就能够了,说时迟那时快。动手写了一个以下实现负载均衡
public class ConsulHttpMessageHandlerBuilder: HttpMessageHandlerBuilder { public ConsulHttpMessageHandlerBuilder(ConsulDiscoveryHttpClientHandler consulDiscoveryHttpClientHandler) { PrimaryHandler = consulDiscoveryHttpClientHandler; } private string _name; public override IList<DelegatingHandler> AdditionalHandlers => new List<DelegatingHandler>(); public override string Name { get => _name; set { if (value == null) { throw new ArgumentNullException(nameof(value)); } _name = value; } } public override HttpMessageHandler PrimaryHandler { get; set; } public override HttpMessageHandler Build() { if (PrimaryHandler == null) { throw new InvalidOperationException(nameof(PrimaryHandler)); } return CreateHandlerPipeline(PrimaryHandler, AdditionalHandlers); } }
相对于原来的代码其实就变更了一点,就是用本身的ConsulDiscoveryHttpClientHandler替换了默认的HttpClientHandler,具体ConsulDiscoveryHttpClientHandler的实现能够参考上篇文章的实现。而后在注册的地方,替换掉默认的DefaultHttpMessageHandlerBuilder。框架
public void ConfigureServices(IServiceCollection services) { services.AddHttpClient(); services.AddTransient<ConsulDiscoveryHttpClientHandler>(); services.Replace(new ServiceDescriptor(typeof(HttpMessageHandlerBuilder),typeof(ConsulHttpMessageHandlerBuilder),ServiceLifetime.Transient)); }
试了下,没毛病,心中暗喜了几秒。可是冷静下来想想,感受不是很合理还要本身写Builder替换默认的方式。不符合开放封闭原则,对原有代码自己的入侵比较大,彷佛不是很合理。要不就说,学习必定要仔细,特别是刚开始的时候,能少踩好多坑。在微软的帮助文档里已经提到了能经过IHttpClientBuilder的扩展方法能够用自定义的实现替换掉默认的PrimaryHandler实例,大体修改注册的地方以下。async
public void ConfigureServices(IServiceCollection services) { services.AddTransient<ConsulDiscoveryHttpClientHandler>(); services.AddHttpClient().ConfigurePrimaryHttpMessageHandler<ConsulDiscoveryHttpClientHandler>();; }
接下来咱们来看看ConfigurePrimaryHttpMessageHandler这个扩展方法到底作了什么,该方法来自HttpClientBuilderExtensions扩展类具体实现以下ide
public static IHttpClientBuilder ConfigurePrimaryHttpMessageHandler<THandler>(this IHttpClientBuilder builder) where THandler : HttpMessageHandler { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } builder.Services.Configure<HttpClientFactoryOptions>(builder.Name, options => { options.HttpMessageHandlerBuilderActions.Add(b => b.PrimaryHandler = b.Services.GetRequiredService<THandler>()); }); return builder; }
而后经过DefaultHttpClientFactory类的CreateHandlerEntry方法里能够看到HttpClientFactoryOptions类的HttpMessageHandlerBuilderActions调用的的地方其实传入的就死当前注册到HttpMessageHandlerBuilder的DefaultHttpMessageHandlerBuilder,大体调用代码以下
internal ActiveHandlerTrackingEntry CreateHandlerEntry(string name) { ..... try { var builder = services.GetRequiredService<HttpMessageHandlerBuilder>(); builder.Name = name; Action<HttpMessageHandlerBuilder> configure = Configure; for (var i = _filters.Length - 1; i >= 0; i--) { configure = _filters[i].Configure(configure); } configure(builder); var handler = new LifetimeTrackingHttpMessageHandler(builder.Build()); return new ActiveHandlerTrackingEntry(name, handler, scope, options.HandlerLifetime); void Configure(HttpMessageHandlerBuilder b) { for (var i = 0; i < options.HttpMessageHandlerBuilderActions.Count; i++) { options.HttpMessageHandlerBuilderActions[i](b); } } } catch { ..... } }
回头来看HttpClientBuilderExtensions扩展类还有一个ConfigureHttpMessageHandlerBuilder扩展方法
public static IHttpClientBuilder AddHttpMessageHandler<THandler>(this IHttpClientBuilder builder) where THandler : DelegatingHandler { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } builder.Services.Configure<HttpClientFactoryOptions>(builder.Name, options => { options.HttpMessageHandlerBuilderActions.Add(b => b.AdditionalHandlers.Add(b.Services.GetRequiredService<THandler>())); }); return builder; }
这个是对DefaultHttpMessageHandlerBuilder附加的Handler作添加操做,那么PrimaryHandler和AdditionalHandlers之间到底有什么关系呢?咱们回过头来看一下DefaultHttpMessageHandlerBuilder类相关方法的具体实现,大体代码以下
public override HttpMessageHandler Build() { if (PrimaryHandler == null) { var message = Resources.FormatHttpMessageHandlerBuilder_PrimaryHandlerIsNull(nameof(PrimaryHandler)); throw new InvalidOperationException(message); } return CreateHandlerPipeline(PrimaryHandler, AdditionalHandlers); } 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)); } var additionalHandlersList = additionalHandlers as IReadOnlyList<DelegatingHandler> ?? additionalHandlers.ToArray(); var next = primaryHandler; for (var i = additionalHandlersList.Count - 1; i >= 0; i--) { var handler = additionalHandlersList[i]; if (handler == null) { var message = Resources.FormatHttpMessageHandlerBuilder_AdditionalHandlerIsNull(nameof(additionalHandlers)); throw new InvalidOperationException(message); } if (handler.InnerHandler != null) { var message = Resources.FormatHttpMessageHandlerBuilder_AdditionHandlerIsInvalid( nameof(DelegatingHandler.InnerHandler), nameof(DelegatingHandler), nameof(HttpMessageHandlerBuilder), Environment.NewLine, handler); throw new InvalidOperationException(message); } handler.InnerHandler = next; next = handler; } return next; }
经过这段代码能够看出原来是用PrimaryHandler和AdditionalHandlers集合构建了一个Handler执行管道,PrimaryHandler做为管道的最后执行点,附加管道按照代码注入的顺序执行。看到这里相信你基本上对HttpClientFactory大体的工做方式就有必定的认知。其实从编码的角度上来说,除非有特殊要求,不然咱们不会替换掉PrimaryHandler,只须要将咱们的Handler添加到AdditionalHandlers集合便可。
经过上面的分析咱们基本上能够动手实现一个最合理的实现方式了
public void ConfigureServices(IServiceCollection services) { //consul地址 services.AddConsul("http://localhost:8500/"); //HttpPClient查找名称(建议使用服务注册名称) services.AddHttpClient("PersonService", c => { //服务注册的名称(建议和HttpPClient查找名称一致) c.BaseAddress = new Uri("http://PersonService/"); }).AddHttpMessageHandler<ConsulDiscoveryDelegatingHandler>(); }
AddConsul扩展方法
public static IServiceCollection AddConsul(this IServiceCollection services, string consulAddress) { services.AddTransient(provider => { return new ConsulClient(x => { // consul 服务地址 x.Address = new Uri(consulAddress); }); }); //注册自定义的DelegatingHandler services.AddTransient<ConsulDiscoveryDelegatingHandler>(); return services; }
自定义的ConsulDiscoveryDelegatingHandler
public class ConsulDiscoveryDelegatingHandler : DelegatingHandler { private readonly ConsulClient _consulClient; private readonly ILogger<ConsulDiscoveryDelegatingHandler> _logger; public ConsulDiscoveryDelegatingHandler(ConsulClient consulClient, ILogger<ConsulDiscoveryDelegatingHandler> logger) { _consulClient = consulClient; _logger = logger; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var current = request.RequestUri; try { //调用的服务地址里的域名(主机名)传入发现的服务名称便可 request.RequestUri = new Uri($"{current.Scheme}://{LookupService(current.Host)}/{current.PathAndQuery}"); return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); } catch (Exception e) { _logger?.LogDebug(e, "Exception during SendAsync()"); throw; } finally { request.RequestUri = current; } } private string LookupService(string serviceName) { var services = _consulClient.Catalog.Service(serviceName).Result.Response; if (services != null && services.Any()) { //模拟负载均衡算法(随机获取一个地址) int index = r.Next(services.Count()); var service = services.ElementAt(index); return $"{service.ServiceAddress}:{service.ServicePort}"); } return null; } }
编写PersonTestController测试代码
public class PersonTestController : Controller { private readonly IHttpClientFactory _clientFactory; public PersonTestController(IHttpClientFactory clientFactory) { _clientFactory = clientFactory; } public async Task<ActionResult<string>> GetPersonInfo(int personId) { var client = _clientFactory.CreateClient("PersonService"); var response = await client.GetAsync($"/Person/Get/{personId}"); var result = await response.Content.ReadAsStringAsync(); return result; } }
经过这两篇文章,主要讲解了HttpClientFactory和HttpClient结合Consul完成服务发现,我的更推荐在后续的开发和实践中采用HttpClientFactory的方式。本文可能重在讲思路,具体的实现方式可能不够精细 。其中还涉及到了部分框架源码,不熟悉源码的话可能某些地方不是很好理解,再加上本人文笔不足,若是带来阅读不便敬请谅解。主要仍是想把本身的理解和思路转达给你们,望批评指导,以便后期改正。