重新整理 .net core 实践篇—————HttpClientFactory[三十二]
前言
简单整理一下HttpClientFactory 。
正文
这个HttpFactory 主要有下面的功能:
管理内部HttpMessageHandler 的生命周期,灵活应对资源问题和DNS刷新问题
支持命名话、类型化配置,集中管理配置,避免冲突。
灵活的出站请求管道配置,轻松管理请求生命周期
内置管道最外层和最内层日志记录器,有information 和 Trace 输出
核心对象:
HttpClient
HttpMessageHandler
SocketsHttpHandler
DelegatingHandler
IHttpClientFactory
IHttpClientBuilder
请求的大概流程图为:
从上图中看到SocketsHttpHandler 才是正确去发起请求。
里面的logginScopeHttpMessageHandler 、CustomMessageHandler 还有LoggingHttpMessageHandler,他们都是中间处理,处于管道之中,可以理解为中间件部分。
logginScopeHttpMessageHandler 是没有经过CustomMessageHandler 的日志,LoggingHttpMessageHandler 是经过CustomMessageHandler 的日志,也就是说LoggingHttpMessageHandler 才是正在去发送请求的数据。
HttpClientFactory 提供了三种创建HttpClient的模式:
创建模式:
工厂模式
命名客户端模式
类型化客户端模式
那么就来看一下各种模式的不同吧。
工厂模式
在.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 属性如上。
效果如下:
其实在我们使用过程中最好去使用这种方式,有两个好处。
不同客户端可以单独配置
不同的可以的生命周期不同,即使一个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。
重新整理 .net core 实践篇—————HttpClientFactory[三十二]的更多相关文章
- 重新整理 .net core 实践篇————网关[三十六]
前言 简单整理一下网关. 正文 在介绍网关之前,介绍一下BFF,BFF全称是Backend For Frontend,它负责认证授权,服务聚合,目标是为前端提供服务. 说的通透一点,就是有没有见过这种 ...
- 重新整理 .net core 实践篇————依赖注入应用[二]
前言 这里介绍一下.net core的依赖注入框架,其中其代码原理在我的另一个整理<<重新整理 1400篇>>中已经写了,故而专门整理应用这一块. 以下只是个人整理,如有问题, ...
- 重新整理 .net core 实践篇—————grpc[三十三]
前言 简单整理一下grpc. 正文 什么是grpc? 一个远程过程调用框架,可以像类一样调用远程方法. 这种模式一般来说就是代理模式,然后都是框架自我生成的. 由google 公司发起并开源,故而前面 ...
- 重新整理 .net core 实践篇—————工作单元模式[二十六]
前言 简单整理一下工作单元模式. 正文 工作单元模式有3个特性,也算是其功能: 使用同一上下文 跟踪实体的状态 保障事务一致性 工作单元模式 主要关注事务,所以重点在事务上. 在共享层的基础建设类库中 ...
- 重新整理 .net core 实践篇——— filter[四十四]
前言 简单介绍一下filter 正文 filter 的种类,微软文档中写道: 每种筛选器类型都在筛选器管道中的不同阶段执行: 授权筛选器最先运行,用于确定是否已针对请求为用户授权. 如果请求未获授权, ...
- 重新整理 .net core 实践篇—————路由和终结点[二十三]
前言 简单整理一下路由和终节点. 正文 路由方式主要有两种: 1.路由模板方式 2.RouteAttribute 方式 路由约束: 1.类型约束 2.范围约束 3.正则表达式 4.是否必选 5.自定义 ...
- 重新整理 .net core 实践篇————配置应用[一]
前言 本来想整理到<<重新整理.net core 计1400篇>>里面去,但是后来一想,整理 .net core 实践篇 是偏于实践,故而分开. 因为是重新整理,那么就从配置开 ...
- NeHe OpenGL教程 第三十二课:拾取游戏
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- Bootstrap <基础三十二>模态框(Modal)插件
模态框(Modal)是覆盖在父窗体上的子窗体.通常,目的是显示来自一个单独的源的内容,可以在不离开父窗体的情况下有一些互动.子窗体可提供信息.交互等. 如果您想要单独引用该插件的功能,那么您需要引用 ...
随机推荐
- ALPHA任务拆解
项目 内容 这个作业属于哪个课程 BUAA2020软件工程 这个作业的要求在哪里 作业要求 我们在这个课程的目标是 学会团队合作,共同开发一个完整的项目 这个作业在哪个具体方面帮助我们实现目标 团队任 ...
- Nifi:nifi的基本使用
Nifi的安装使用 Nifi安装 首先说一下Nifi的安装,这里Nifi可以支持Windows版和Linux,只需要去官网:http://nifi.apache.org/ 根据自己需要的版本,选择下载 ...
- margin (子元素远离父元素边框)
如果父盒子没有设置border框着,那么他的子元素无法利用margin-top 来远离父元素的上边框 如果使用了margin-top会使子元素和父元素一起往下移 (子想离,父不设置border边框 则 ...
- 自定义元类 __call__,__init__,__new__总结
只要对象能被调用 产生对象的类里必然有__call__方法 在调用类时,必定先触发type里的__call__ __call__下有: 1.产生对象的object.__new__ 2..被调用的类自己 ...
- Docker部署微服务项目
测试包准备工作 1.spring.io或者ide创建demo工程 spring官网 2.本地demo代码,打包成jar包 使用Dockerfile构建微服务镜像 3.将jar包上传到你的vps lin ...
- Taro使用多线程Worker相关问题解决
JavaScript 语言采用的是单线程模型,HTML5标准中的Web Worker ,为 JavaScript 创造多线程环境.微信小程序也有相应的Worker,同样具备多线程运行的能力 主页面中创 ...
- 在Visual Studio 中使用git——浏览版本库(七)
在Visual Studio 中使用git--什么是Git(一) 在Visual Studio 中使用git--给Visual Studio安装 git插件(二) 在Visual Studio 中使用 ...
- [bug] eclipse 点击new菜单后没有新建java project或class选项
参考 https://blog.csdn.net/u013001763/article/details/54405758
- 查阅日志文件:有时候报错信息只是给出了问题的表面现象,要想更深入的了解问题,必须查看相应的日志文件,而日志文件又分为系统日志文件(/var/log)和应用的日志文件,结合这两个日志文件,一般就能定位问题所在。
作为一名合格的 Linux 运维工程师,一定要有一套清晰.明确的解决故障思路,当问题出现时,才能迅速定位.解决问题,这里给出一个处理问题的一般思路: 重视报错提示信息:每个错误的出现,都是给出错误提示 ...
- DOCKER学习_018:Docker-Compose文件简介
Docker-Compose文件 通过之前的示例,其实我们可以看到,所有服务的管理,都是依靠docker-compose.yml文件来实现的.那么我们接下来就详细说一说docker-compose.y ...