前言
簡單整理一下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。