1- using System ;
1+ using Soenneker . Dtos . HttpClientOptions ;
2+ using Soenneker . Extensions . Enumerable ;
3+ using Soenneker . Extensions . ValueTask ;
4+ using Soenneker . Utils . HttpClientCache . Abstract ;
5+ using Soenneker . Utils . Runtime ;
6+ using Soenneker . Utils . SingletonDictionary ;
7+ using System ;
8+ using System . Collections . Concurrent ;
29using System . Collections . Generic ;
310using System . Net ;
411using System . Net . Http ;
12+ using System . Runtime . CompilerServices ;
513using System . Threading ;
614using System . Threading . Tasks ;
7- using Soenneker . Dtos . HttpClientOptions ;
8- using Soenneker . Extensions . ValueTask ;
9- using Soenneker . Utils . HttpClientCache . Abstract ;
10- using Soenneker . Utils . Runtime ;
11- using Soenneker . Utils . SingletonDictionary ;
1215
1316namespace Soenneker . Utils . HttpClientCache ;
1417
@@ -17,6 +20,8 @@ public class HttpClientCache : IHttpClientCache
1720{
1821 private readonly SingletonDictionary < HttpClient > _httpClients ;
1922
23+ private readonly ConcurrentDictionary < HandlerKey , SocketsHttpHandler > _handlers = new ( ) ;
24+
2025 public HttpClientCache ( )
2126 {
2227 _httpClients = new SingletonDictionary < HttpClient > ( async args =>
@@ -43,14 +48,14 @@ public HttpClientCache()
4348 } ) ;
4449 }
4550
46- private static HttpClient CreateHttpClient ( HttpClientOptions ? options )
51+ private HttpClient CreateHttpClient ( HttpClientOptions ? options )
4752 {
4853 if ( RuntimeUtil . IsBrowser ( ) )
4954 {
5055 return options ? . HttpClientHandler != null ? new HttpClient ( options . HttpClientHandler ) : new HttpClient ( ) ;
5156 }
5257
53- return options ? . HttpClientHandler != null ? new HttpClient ( options . HttpClientHandler ) : new HttpClient ( CreateSocketsHttpHandler ( options ) ) ;
58+ return options ? . HttpClientHandler != null ? new HttpClient ( options . HttpClientHandler ) : new HttpClient ( GetOrCreateHandler ( options ) ) ;
5459 }
5560
5661 public ValueTask < HttpClient > Get ( string id , HttpClientOptions ? options = null , CancellationToken cancellationToken = default )
@@ -85,38 +90,53 @@ public HttpClient GetSync(string id, Func<HttpClientOptions?> optionsFactory, Ca
8590 return _httpClients . GetSync ( id , cancellationToken , options ) ;
8691 }
8792
93+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
8894 private static async ValueTask ConfigureHttpClient ( HttpClient httpClient , HttpClientOptions ? options )
8995 {
9096 httpClient . Timeout = options ? . Timeout ?? TimeSpan . FromSeconds ( 100 ) ;
9197
9298 if ( options ? . BaseAddress != null )
93- httpClient . BaseAddress = new Uri ( options . BaseAddress ) ;
99+ {
100+ Uri baseUri = new ( options . BaseAddress ) ;
101+
102+ if ( ! Equals ( httpClient . BaseAddress , baseUri ) )
103+ httpClient . BaseAddress = baseUri ;
104+ }
94105
95106 AddDefaultRequestHeaders ( httpClient , options ? . DefaultRequestHeaders ) ;
96107
97- if ( options ? . ModifyClient != null )
98- await options . ModifyClient . Invoke ( httpClient ) . NoSync ( ) ;
108+ Func < HttpClient , ValueTask > ? modifyClient = options ? . ModifyClient ;
109+
110+ if ( modifyClient is not null )
111+ await modifyClient ( httpClient ) . NoSync ( ) ;
99112 }
100113
101- private static SocketsHttpHandler CreateSocketsHttpHandler ( HttpClientOptions ? options )
114+ private SocketsHttpHandler GetOrCreateHandler ( HttpClientOptions ? options )
102115 {
103- var handler = new SocketsHttpHandler
104- {
105- PooledConnectionLifetime = options ? . PooledConnectionLifetime ?? TimeSpan . FromMinutes ( 10 ) ,
106- MaxConnectionsPerServer = options ? . MaxConnectionsPerServer ?? 40
107- } ;
116+ var key = new HandlerKey (
117+ options ? . PooledConnectionLifetime ? . TotalSeconds ?? 600 ,
118+ options ? . MaxConnectionsPerServer ?? 40 ,
119+ options ? . UseCookieContainer == true
120+ ) ;
108121
109- if ( options ? . UseCookieContainer == true )
122+ return _handlers . GetOrAdd ( key , _ =>
110123 {
111- handler . CookieContainer = new CookieContainer ( ) ;
112- }
124+ var handler = new SocketsHttpHandler
125+ {
126+ PooledConnectionLifetime = TimeSpan . FromSeconds ( key . LifetimeSeconds ) ,
127+ MaxConnectionsPerServer = key . MaxConnections
128+ } ;
129+
130+ if ( key . UseCookies )
131+ handler . CookieContainer = new CookieContainer ( ) ;
113132
114- return handler ;
133+ return handler ;
134+ } ) ;
115135 }
116136
117137 private static void AddDefaultRequestHeaders ( HttpClient httpClient , Dictionary < string , string > ? headers )
118138 {
119- if ( headers == null )
139+ if ( headers . IsNullOrEmpty ( ) )
120140 return ;
121141
122142 foreach ( KeyValuePair < string , string > header in headers )
@@ -138,12 +158,26 @@ public void RemoveSync(string id)
138158 public ValueTask DisposeAsync ( )
139159 {
140160 GC . SuppressFinalize ( this ) ;
161+
162+ foreach ( KeyValuePair < HandlerKey , SocketsHttpHandler > kvp in _handlers . ToArray ( ) )
163+ {
164+ if ( _handlers . TryRemove ( kvp . Key , out SocketsHttpHandler ? handler ) )
165+ handler . Dispose ( ) ;
166+ }
167+
141168 return _httpClients . DisposeAsync ( ) ;
142169 }
143170
144171 public void Dispose ( )
145172 {
146173 GC . SuppressFinalize ( this ) ;
174+
175+ foreach ( KeyValuePair < HandlerKey , SocketsHttpHandler > kvp in _handlers . ToArray ( ) )
176+ {
177+ if ( _handlers . TryRemove ( kvp . Key , out SocketsHttpHandler ? handler ) )
178+ handler . Dispose ( ) ;
179+ }
180+
147181 _httpClients . Dispose ( ) ;
148182 }
149183}
0 commit comments