完善HttpClientFactory

This commit is contained in:
老九 2021-09-29 21:37:51 +08:00
parent 21a1d85af7
commit d41eec7445
5 changed files with 244 additions and 29 deletions

View File

@ -32,7 +32,7 @@ namespace FastGithub.Http
/// </summary>
/// <param name="handler"></param>
/// <param name="disposeHandler"></param>
internal HttpClient(HttpClientHandler handler, bool disposeHandler)
public HttpClient(HttpMessageHandler handler, bool disposeHandler)
: base(handler, disposeHandler)
{
}

View File

@ -1,6 +1,6 @@
using FastGithub.Configuration;
using FastGithub.DomainResolve;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Concurrent;
namespace FastGithub.Http
@ -11,19 +11,29 @@ namespace FastGithub.Http
sealed class HttpClientFactory : IHttpClientFactory
{
private readonly IDomainResolver domainResolver;
private ConcurrentDictionary<DomainConfig, HttpClientHandler> domainHandlers = new();
/// <summary>
/// httpHandler的生命周期
/// </summary>
private readonly TimeSpan lifeTime = TimeSpan.FromMinutes(2d);
/// <summary>
/// HttpHandler清理器
/// </summary>
private readonly LifetimeHttpHandlerCleaner httpHandlerCleaner = new();
/// <summary>
/// LazyOf(LifetimeHttpHandler)缓存
/// </summary>
private readonly ConcurrentDictionary<DomainConfig, Lazy<LifetimeHttpHandler>> httpHandlerLazyCache = new();
/// <summary>
/// HttpClient工厂
/// </summary>
/// <param name="domainResolver"></param>
/// <param name="options"></param>
public HttpClientFactory(
IDomainResolver domainResolver,
IOptionsMonitor<FastGithubOptions> options)
public HttpClientFactory(IDomainResolver domainResolver)
{
this.domainResolver = domainResolver;
options.OnChange(opt => this.domainHandlers = new());
}
/// <summary>
@ -33,8 +43,41 @@ namespace FastGithub.Http
/// <returns></returns>
public HttpClient CreateHttpClient(DomainConfig domainConfig)
{
var httpClientHandler = this.domainHandlers.GetOrAdd(domainConfig, config => new HttpClientHandler(config, this.domainResolver));
return new HttpClient(httpClientHandler, disposeHandler: false);
var lifetimeHttpHandlerLazy = this.httpHandlerLazyCache.GetOrAdd(domainConfig, this.CreateLifetimeHttpHandlerLazy);
var lifetimeHttpHandler = lifetimeHttpHandlerLazy.Value;
return new HttpClient(lifetimeHttpHandler, disposeHandler: false);
}
/// <summary>
/// 创建LazyOf(LifetimeHttpHandler)
/// </summary>
/// <param name="domainConfig"></param>
/// <returns></returns>
private Lazy<LifetimeHttpHandler> CreateLifetimeHttpHandlerLazy(DomainConfig domainConfig)
{
return new Lazy<LifetimeHttpHandler>(() => this.CreateLifetimeHttpHandler(domainConfig), true);
}
/// <summary>
/// 创建LifetimeHttpHandler
/// </summary>
/// <returns></returns>
private LifetimeHttpHandler CreateLifetimeHttpHandler(DomainConfig domainConfig)
{
var httpClientHandler = new HttpClientHandler(domainConfig, this.domainResolver);
return new LifetimeHttpHandler(httpClientHandler, this.lifeTime, this.OnLifetimeHttpHandlerDeactivate);
}
/// <summary>
/// 当有httpHandler失效时
/// </summary>
/// <param name="lifetimeHttpHandler">httpHandler</param>
private void OnLifetimeHttpHandlerDeactivate(LifetimeHttpHandler lifetimeHttpHandler)
{
// 切换激活状态的记录的实例
var domainConfig = ((HttpClientHandler)lifetimeHttpHandler.InnerHandler!).DomainConfig;
this.httpHandlerLazyCache[domainConfig] = this.CreateLifetimeHttpHandlerLazy(domainConfig);
this.httpHandlerCleaner.Add(lifetimeHttpHandler);
}
}
}

View File

@ -21,10 +21,14 @@ namespace FastGithub.Http
/// </summary>
class HttpClientHandler : DelegatingHandler
{
private readonly DomainConfig domainConfig;
private readonly IDomainResolver domainResolver;
private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(10d);
/// <summary>
/// 获取域名配置
/// </summary>
public DomainConfig DomainConfig { get; }
/// <summary>
/// HttpClientHandler
/// </summary>
@ -33,7 +37,7 @@ namespace FastGithub.Http
public HttpClientHandler(DomainConfig domainConfig, IDomainResolver domainResolver)
{
this.domainResolver = domainResolver;
this.domainConfig = domainConfig;
this.DomainConfig = domainConfig;
this.InnerHandler = this.CreateSocketsHttpHandler();
}
@ -53,23 +57,16 @@ namespace FastGithub.Http
// 请求上下文信息
var isHttps = uri.Scheme == Uri.UriSchemeHttps;
var tlsSniValue = this.domainConfig.GetTlsSniPattern().WithDomain(uri.Host).WithRandom();
var tlsSniValue = this.DomainConfig.GetTlsSniPattern().WithDomain(uri.Host).WithRandom();
request.SetRequestContext(new RequestContext(isHttps, tlsSniValue));
// 设置请求头host修改协议为http使用ip取代域名
var address = await this.domainResolver.ResolveAnyAsync(uri.Host, cancellationToken);
var uriBuilder = new UriBuilder(uri)
{
Scheme = Uri.UriSchemeHttp,
Host = address.ToString()
};
// 设置请求头host修改协议为http
request.Headers.Host = uri.Host;
request.RequestUri = uriBuilder.Uri;
request.RequestUri = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri;
if (this.domainConfig.Timeout != null)
if (this.DomainConfig.Timeout != null)
{
using var timeoutTokenSource = new CancellationTokenSource(this.domainConfig.Timeout.Value);
using var timeoutTokenSource = new CancellationTokenSource(this.DomainConfig.Timeout.Value);
using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);
return await base.SendAsync(request, linkedTokenSource.Token);
}
@ -102,8 +99,7 @@ namespace FastGithub.Http
private async ValueTask<Stream> ConnectCallback(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
var innerExceptions = new List<Exception>();
var dnsEndPoint = new DnsEndPoint(context.InitialRequestMessage.Headers.Host!, context.DnsEndPoint.Port);
var ipEndPoints = this.GetIPEndPointsAsync(dnsEndPoint, cancellationToken);
var ipEndPoints = this.GetIPEndPointsAsync(context.DnsEndPoint, cancellationToken);
await foreach (var ipEndPoint in ipEndPoints)
{
@ -161,12 +157,12 @@ namespace FastGithub.Http
{
if (errors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch))
{
if (this.domainConfig.TlsIgnoreNameMismatch == true)
if (this.DomainConfig.TlsIgnoreNameMismatch == true)
{
return true;
}
var domain = context.InitialRequestMessage.Headers.Host!;
var domain = context.DnsEndPoint.Host;
var dnsNames = ReadDnsNames(cert);
return dnsNames.Any(dns => IsMatch(dns, domain));
}
@ -183,7 +179,7 @@ namespace FastGithub.Http
/// <returns></returns>
private async IAsyncEnumerable<IPEndPoint> GetIPEndPointsAsync(DnsEndPoint dnsEndPoint, [EnumeratorCancellation] CancellationToken cancellationToken)
{
if (IPAddress.TryParse(this.domainConfig.IPAddress, out var address) ||
if (IPAddress.TryParse(this.DomainConfig.IPAddress, out var address) ||
IPAddress.TryParse(dnsEndPoint.Host, out address))
{
yield return new IPEndPoint(address, dnsEndPoint.Port);

View File

@ -0,0 +1,44 @@
using System;
using System.Net.Http;
using System.Threading;
namespace FastGithub.Http
{
/// <summary>
/// 表示自主管理生命周期的的HttpMessageHandler
/// </summary>
sealed class LifetimeHttpHandler : DelegatingHandler
{
private readonly Timer timer;
/// <summary>
/// 具有生命周期的HttpHandler
/// </summary>
/// <param name="handler">HttpHandler</param>
/// <param name="lifeTime">拦截器的生命周期</param>
/// <param name="deactivateAction">失效回调</param>
public LifetimeHttpHandler(HttpMessageHandler handler, TimeSpan lifeTime, Action<LifetimeHttpHandler> deactivateAction)
: base(handler)
{
this.timer = new Timer(this.OnTimerCallback, deactivateAction, lifeTime, Timeout.InfiniteTimeSpan);
}
/// <summary>
/// timer触发时
/// </summary>
/// <param name="state"></param>
private void OnTimerCallback(object? state)
{
this.timer.Dispose();
((Action<LifetimeHttpHandler>)(state!))(this);
}
/// <summary>
/// 这里不释放资源
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
}
}
}

View File

@ -0,0 +1,132 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace FastGithub.Http
{
/// <summary>
/// 表示LifetimeHttpHandler清理器
/// </summary>
sealed class LifetimeHttpHandlerCleaner
{
/// <summary>
/// 当前监视生命周期的记录的数量
/// </summary>
private int trackingEntryCount = 0;
/// <summary>
/// 监视生命周期的记录队列
/// </summary>
private readonly ConcurrentQueue<TrackingEntry> trackingEntries = new();
/// <summary>
/// 获取或设置清理的时间间隔
/// 默认10s
/// </summary>
public TimeSpan CleanupInterval { get; set; } = TimeSpan.FromSeconds(10d);
/// <summary>
/// 添加要清除的httpHandler
/// </summary>
/// <param name="handler">httpHandler</param>
public void Add(LifetimeHttpHandler handler)
{
var entry = new TrackingEntry(handler);
this.trackingEntries.Enqueue(entry);
// 从0变为1要启动清理作业
if (Interlocked.Increment(ref this.trackingEntryCount) == 1)
{
this.StartCleanup();
}
}
/// <summary>
/// 启动清理作业
/// </summary>
private async void StartCleanup()
{
while (this.Cleanup() == false)
{
await Task.Delay(this.CleanupInterval);
}
}
/// <summary>
/// 清理失效的拦截器
/// 返回是否完全清理
/// </summary>
/// <returns></returns>
private bool Cleanup()
{
var cleanCount = this.trackingEntries.Count;
for (var i = 0; i < cleanCount; i++)
{
this.trackingEntries.TryDequeue(out var entry);
Debug.Assert(entry != null);
if (entry.CanDispose == false)
{
this.trackingEntries.Enqueue(entry);
continue;
}
entry.Dispose();
if (Interlocked.Decrement(ref this.trackingEntryCount) == 0)
{
return true;
}
}
return false;
}
/// <summary>
/// 表示监视生命周期的记录
/// </summary>
private class TrackingEntry : IDisposable
{
/// <summary>
/// 用于释放资源的对象
/// </summary>
private readonly IDisposable disposable;
/// <summary>
/// 监视对象的弱引用
/// </summary>
private readonly WeakReference weakReference;
/// <summary>
/// 获取是否可以释放资源
/// </summary>
/// <returns></returns>
public bool CanDispose => this.weakReference.IsAlive == false;
/// <summary>
/// 监视生命周期的记录
/// </summary>
/// <param name="handler">激活状态的httpHandler</param>
public TrackingEntry(LifetimeHttpHandler handler)
{
this.disposable = handler.InnerHandler!;
this.weakReference = new WeakReference(handler);
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
try
{
this.disposable.Dispose();
}
catch (Exception)
{
}
}
}
}
}