فهرست منبع

完善HttpClientFactory

老九 3 سال پیش
والد
کامیت
d41eec7445

+ 1 - 1
FastGithub.Http/HttpClient.cs

@@ -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)
         {
         }

+ 52 - 9
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<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);
         }
     }
 }

+ 15 - 19
FastGithub.Http/HttpClientHandler.cs

@@ -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 - 0
FastGithub.Http/LifetimeHttpHandler.cs

@@ -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 - 0
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
+{
+    /// <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)
+                {
+                }
+            }
+        }
+    }
+}