完善HttpClientFactory
This commit is contained in:
parent
21a1d85af7
commit
d41eec7445
@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
44
FastGithub.Http/LifetimeHttpHandler.cs
Normal file
44
FastGithub.Http/LifetimeHttpHandler.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
132
FastGithub.Http/LifetimeHttpHandlerCleaner.cs
Normal file
132
FastGithub.Http/LifetimeHttpHandlerCleaner.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user