33using Polly ;
44using Polly . Wrap ;
55using System ;
6+ using System . Collections . Concurrent ;
67using System . Collections . Generic ;
78using System . Linq ;
89using System . Net ;
@@ -21,13 +22,13 @@ public class ResilientHttpClient : IHttpClient
2122 {
2223 private readonly HttpClient _client ;
2324 private readonly ILogger < ResilientHttpClient > _logger ;
24- private PolicyWrap _policyWrap ;
25+ private ConcurrentDictionary < string , PolicyWrap > _policyWrappers ;
2526
26- public ResilientHttpClient ( IEnumerable < Policy > policies , ILogger < ResilientHttpClient > logger )
27+ public ResilientHttpClient ( ILogger < ResilientHttpClient > logger )
2728 {
2829 _client = new HttpClient ( ) ;
2930 _logger = logger ;
30- _policyWrap = Policy . Wrap ( policies . ToArray ( ) ) ;
31+ _policyWrappers = new ConcurrentDictionary < string , PolicyWrap > ( ) ;
3132 }
3233
3334
@@ -93,43 +94,49 @@ private Task<HttpResponseMessage> DoPostPutAsync<T>(HttpMethod method, string ur
9394 // as it is disposed after each call
9495 var origin = GetOriginFromUri ( uri ) ;
9596
96- return HttpInvoker ( origin , async ( ) =>
97- {
98- var requestMessage = new HttpRequestMessage ( method , uri ) ;
97+ return HttpInvoker ( origin , ( ) =>
98+ {
99+ var requestMessage = new HttpRequestMessage ( method , uri ) ;
99100
100- requestMessage . Content = new StringContent ( JsonConvert . SerializeObject ( item ) , System . Text . Encoding . UTF8 , "application/json" ) ;
101+ requestMessage . Content = new StringContent ( JsonConvert . SerializeObject ( item ) , System . Text . Encoding . UTF8 , "application/json" ) ;
101102
102- if ( authorizationToken != null )
103- {
104- requestMessage . Headers . Authorization = new AuthenticationHeaderValue ( authorizationMethod , authorizationToken ) ;
105- }
103+ if ( authorizationToken != null )
104+ {
105+ requestMessage . Headers . Authorization = new AuthenticationHeaderValue ( authorizationMethod , authorizationToken ) ;
106+ }
106107
107- if ( requestId != null )
108- {
109- requestMessage . Headers . Add ( "x-requestid" , requestId ) ;
110- }
108+ if ( requestId != null )
109+ {
110+ requestMessage . Headers . Add ( "x-requestid" , requestId ) ;
111+ }
111112
112- var response = await _client . SendAsync ( requestMessage ) ;
113+ var response = _client . SendAsync ( requestMessage ) . Result ;
113114
114- // raise exception if HttpResponseCode 500
115- // needed for circuit breaker to track fails
115+ // raise exception if HttpResponseCode 500
116+ // needed for circuit breaker to track fails
116117
117- if ( response . StatusCode == HttpStatusCode . InternalServerError )
118- {
119- throw new HttpRequestException ( ) ;
120- }
118+ if ( response . StatusCode == HttpStatusCode . InternalServerError )
119+ {
120+ throw new HttpRequestException ( ) ;
121+ }
121122
122- return response ;
123- } ) ;
123+ return Task . FromResult ( response ) ;
124+ } ) ;
124125 }
125126
126- private Task < T > HttpInvoker < T > ( string origin , Func < Task < T > > action )
127+ private async Task < T > HttpInvoker < T > ( string origin , Func < Task < T > > action )
127128 {
128129 var normalizedOrigin = NormalizeOrigin ( origin ) ;
129130
131+ if ( ! _policyWrappers . TryGetValue ( normalizedOrigin , out PolicyWrap policyWrap ) )
132+ {
133+ policyWrap = Policy . Wrap ( CreatePolicies ( ) ) ;
134+ _policyWrappers . TryAdd ( normalizedOrigin , policyWrap ) ;
135+ }
136+
130137 // Executes the action applying all
131138 // the policies defined in the wrapper
132- return _policyWrap . ExecuteAsync ( ( ) => action ( ) , new Context ( normalizedOrigin ) ) ;
139+ return await policyWrap . Execute ( action , new Context ( normalizedOrigin ) ) ;
133140 }
134141
135142
@@ -146,5 +153,45 @@ private static string GetOriginFromUri(string uri)
146153
147154 return origin ;
148155 }
156+
157+
158+ private Policy [ ] CreatePolicies ( )
159+ {
160+ return new Policy [ ]
161+ {
162+ Policy . Handle < HttpRequestException > ( )
163+ . WaitAndRetry (
164+ // number of retries
165+ 6 ,
166+ // exponential backofff
167+ retryAttempt => TimeSpan . FromSeconds ( Math . Pow ( 2 , retryAttempt ) ) ,
168+ // on retry
169+ ( exception , timeSpan , retryCount , context ) =>
170+ {
171+ var msg = $ "Retry { retryCount } implemented with Polly's RetryPolicy " +
172+ $ "of { context . PolicyKey } " +
173+ $ "at { context . ExecutionKey } , " +
174+ $ "due to: { exception } .";
175+ _logger . LogWarning ( msg ) ;
176+ _logger . LogDebug ( msg ) ;
177+ } ) ,
178+ Policy . Handle < HttpRequestException > ( )
179+ . CircuitBreaker (
180+ // number of exceptions before breaking circuit
181+ 5 ,
182+ // time circuit opened before retry
183+ TimeSpan . FromMinutes ( 1 ) ,
184+ ( exception , duration ) =>
185+ {
186+ // on circuit opened
187+ _logger . LogTrace ( "Circuit breaker opened" ) ;
188+ } ,
189+ ( ) =>
190+ {
191+ // on circuit closed
192+ _logger . LogTrace ( "Circuit breaker reset" ) ;
193+ } )
194+ } ;
195+ }
149196 }
150197}
0 commit comments