diff --git a/FastGithub.Http/HttpClient.cs b/FastGithub.Http/HttpClient.cs
index 910fb19..07074bd 100644
--- a/FastGithub.Http/HttpClient.cs
+++ b/FastGithub.Http/HttpClient.cs
@@ -32,7 +32,7 @@ namespace FastGithub.Http
///
///
///
- internal HttpClient(HttpClientHandler handler, bool disposeHandler)
+ public HttpClient(HttpMessageHandler handler, bool disposeHandler)
: base(handler, disposeHandler)
{
}
diff --git a/FastGithub.Http/HttpClientFactory.cs b/FastGithub.Http/HttpClientFactory.cs
index 796bcbf..01ed721 100644
--- a/FastGithub.Http/HttpClientFactory.cs
+++ b/FastGithub.Http/HttpClientFactory.cs
@@ -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 domainHandlers = new();
+
+ ///
+ /// httpHandler的生命周期
+ ///
+ private readonly TimeSpan lifeTime = TimeSpan.FromMinutes(2d);
+
+ ///
+ /// HttpHandler清理器
+ ///
+ private readonly LifetimeHttpHandlerCleaner httpHandlerCleaner = new();
+
+ ///
+ /// LazyOf(LifetimeHttpHandler)缓存
+ ///
+ private readonly ConcurrentDictionary> httpHandlerLazyCache = new();
///
/// HttpClient工厂
///
///
- ///
- public HttpClientFactory(
- IDomainResolver domainResolver,
- IOptionsMonitor options)
+ public HttpClientFactory(IDomainResolver domainResolver)
{
this.domainResolver = domainResolver;
- options.OnChange(opt => this.domainHandlers = new());
}
///
@@ -33,8 +43,41 @@ namespace FastGithub.Http
///
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);
+ }
+
+ ///
+ /// 创建LazyOf(LifetimeHttpHandler)
+ ///
+ ///
+ ///
+ private Lazy CreateLifetimeHttpHandlerLazy(DomainConfig domainConfig)
+ {
+ return new Lazy(() => this.CreateLifetimeHttpHandler(domainConfig), true);
+ }
+
+ ///
+ /// 创建LifetimeHttpHandler
+ ///
+ ///
+ private LifetimeHttpHandler CreateLifetimeHttpHandler(DomainConfig domainConfig)
+ {
+ var httpClientHandler = new HttpClientHandler(domainConfig, this.domainResolver);
+ return new LifetimeHttpHandler(httpClientHandler, this.lifeTime, this.OnLifetimeHttpHandlerDeactivate);
+ }
+
+ ///
+ /// 当有httpHandler失效时
+ ///
+ /// httpHandler
+ private void OnLifetimeHttpHandlerDeactivate(LifetimeHttpHandler lifetimeHttpHandler)
+ {
+ // 切换激活状态的记录的实例
+ var domainConfig = ((HttpClientHandler)lifetimeHttpHandler.InnerHandler!).DomainConfig;
+ this.httpHandlerLazyCache[domainConfig] = this.CreateLifetimeHttpHandlerLazy(domainConfig);
+ this.httpHandlerCleaner.Add(lifetimeHttpHandler);
}
}
}
\ No newline at end of file
diff --git a/FastGithub.Http/HttpClientHandler.cs b/FastGithub.Http/HttpClientHandler.cs
index 97ef818..2b23dab 100644
--- a/FastGithub.Http/HttpClientHandler.cs
+++ b/FastGithub.Http/HttpClientHandler.cs
@@ -21,10 +21,14 @@ namespace FastGithub.Http
///
class HttpClientHandler : DelegatingHandler
{
- private readonly DomainConfig domainConfig;
private readonly IDomainResolver domainResolver;
private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(10d);
+ ///
+ /// 获取域名配置
+ ///
+ public DomainConfig DomainConfig { get; }
+
///
/// HttpClientHandler
///
@@ -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 ConnectCallback(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
var innerExceptions = new List();
- 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
///
private async IAsyncEnumerable 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);
diff --git a/FastGithub.Http/LifetimeHttpHandler.cs b/FastGithub.Http/LifetimeHttpHandler.cs
new file mode 100644
index 0000000..3bd6699
--- /dev/null
+++ b/FastGithub.Http/LifetimeHttpHandler.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Net.Http;
+using System.Threading;
+
+namespace FastGithub.Http
+{
+ ///
+ /// 表示自主管理生命周期的的HttpMessageHandler
+ ///
+ sealed class LifetimeHttpHandler : DelegatingHandler
+ {
+ private readonly Timer timer;
+
+ ///
+ /// 具有生命周期的HttpHandler
+ ///
+ /// HttpHandler
+ /// 拦截器的生命周期
+ /// 失效回调
+ public LifetimeHttpHandler(HttpMessageHandler handler, TimeSpan lifeTime, Action deactivateAction)
+ : base(handler)
+ {
+ this.timer = new Timer(this.OnTimerCallback, deactivateAction, lifeTime, Timeout.InfiniteTimeSpan);
+ }
+
+ ///
+ /// timer触发时
+ ///
+ ///
+ private void OnTimerCallback(object? state)
+ {
+ this.timer.Dispose();
+ ((Action)(state!))(this);
+ }
+
+ ///
+ /// 这里不释放资源
+ ///
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ }
+ }
+}
diff --git a/FastGithub.Http/LifetimeHttpHandlerCleaner.cs b/FastGithub.Http/LifetimeHttpHandlerCleaner.cs
new file mode 100644
index 0000000..b77fe0d
--- /dev/null
+++ b/FastGithub.Http/LifetimeHttpHandlerCleaner.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace FastGithub.Http
+{
+ ///
+ /// 表示LifetimeHttpHandler清理器
+ ///
+ sealed class LifetimeHttpHandlerCleaner
+ {
+ ///
+ /// 当前监视生命周期的记录的数量
+ ///
+ private int trackingEntryCount = 0;
+
+ ///
+ /// 监视生命周期的记录队列
+ ///
+ private readonly ConcurrentQueue trackingEntries = new();
+
+ ///
+ /// 获取或设置清理的时间间隔
+ /// 默认10s
+ ///
+ public TimeSpan CleanupInterval { get; set; } = TimeSpan.FromSeconds(10d);
+
+ ///
+ /// 添加要清除的httpHandler
+ ///
+ /// httpHandler
+ 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();
+ }
+ }
+
+ ///
+ /// 启动清理作业
+ ///
+ private async void StartCleanup()
+ {
+ while (this.Cleanup() == false)
+ {
+ await Task.Delay(this.CleanupInterval);
+ }
+ }
+
+ ///
+ /// 清理失效的拦截器
+ /// 返回是否完全清理
+ ///
+ ///
+ 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;
+ }
+
+
+ ///
+ /// 表示监视生命周期的记录
+ ///
+ private class TrackingEntry : IDisposable
+ {
+ ///
+ /// 用于释放资源的对象
+ ///
+ private readonly IDisposable disposable;
+
+ ///
+ /// 监视对象的弱引用
+ ///
+ private readonly WeakReference weakReference;
+
+ ///
+ /// 获取是否可以释放资源
+ ///
+ ///
+ public bool CanDispose => this.weakReference.IsAlive == false;
+
+ ///
+ /// 监视生命周期的记录
+ ///
+ /// 激活状态的httpHandler
+ public TrackingEntry(LifetimeHttpHandler handler)
+ {
+ this.disposable = handler.InnerHandler!;
+ this.weakReference = new WeakReference(handler);
+ }
+
+ ///
+ /// 释放资源
+ ///
+ public void Dispose()
+ {
+ try
+ {
+ this.disposable.Dispose();
+ }
+ catch (Exception)
+ {
+ }
+ }
+ }
+ }
+}