diff --git a/src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs b/src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs index d1b34f49a..d8ee11d77 100644 --- a/src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs +++ b/src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json; using Polly; using Polly.Wrap; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; @@ -21,13 +22,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http { private readonly HttpClient _client; private readonly ILogger _logger; - private PolicyWrap _policyWrap; + private ConcurrentDictionary _policyWrappers; - public ResilientHttpClient(IEnumerable policies, ILogger logger) + public ResilientHttpClient(ILogger logger) { _client = new HttpClient(); _logger = logger; - _policyWrap = Policy.Wrap(policies.ToArray()); + _policyWrappers = new ConcurrentDictionary(); } @@ -93,43 +94,49 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http // as it is disposed after each call var origin = GetOriginFromUri(uri); - return HttpInvoker(origin, async () => - { - var requestMessage = new HttpRequestMessage(method, uri); + return HttpInvoker(origin, () => + { + var requestMessage = new HttpRequestMessage(method, uri); - requestMessage.Content = new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json"); + requestMessage.Content = new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json"); - if (authorizationToken != null) - { - requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken); - } + if (authorizationToken != null) + { + requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken); + } - if (requestId != null) - { - requestMessage.Headers.Add("x-requestid", requestId); - } + if (requestId != null) + { + requestMessage.Headers.Add("x-requestid", requestId); + } - var response = await _client.SendAsync(requestMessage); + var response = _client.SendAsync(requestMessage).Result; - // raise exception if HttpResponseCode 500 - // needed for circuit breaker to track fails + // raise exception if HttpResponseCode 500 + // needed for circuit breaker to track fails - if (response.StatusCode == HttpStatusCode.InternalServerError) - { - throw new HttpRequestException(); - } + if (response.StatusCode == HttpStatusCode.InternalServerError) + { + throw new HttpRequestException(); + } - return response; - }); + return Task.FromResult(response); + }); } - private Task HttpInvoker(string origin, Func> action) + private async Task HttpInvoker(string origin, Func> action) { var normalizedOrigin = NormalizeOrigin(origin); + if (!_policyWrappers.TryGetValue(normalizedOrigin, out PolicyWrap policyWrap)) + { + policyWrap = Policy.Wrap(CreatePolicies()); + _policyWrappers.TryAdd(normalizedOrigin, policyWrap); + } + // Executes the action applying all // the policies defined in the wrapper - return _policyWrap.ExecuteAsync(() => action(), new Context(normalizedOrigin)); + return await policyWrap.Execute(action, new Context(normalizedOrigin)); } @@ -146,5 +153,45 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http return origin; } + + + private Policy[] CreatePolicies() + { + return new Policy[] + { + Policy.Handle() + .WaitAndRetry( + // number of retries + 6, + // exponential backofff + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + // on retry + (exception, timeSpan, retryCount, context) => + { + var msg = $"Retry {retryCount} implemented with Polly's RetryPolicy " + + $"of {context.PolicyKey} " + + $"at {context.ExecutionKey}, " + + $"due to: {exception}."; + _logger.LogWarning(msg); + _logger.LogDebug(msg); + }), + Policy.Handle() + .CircuitBreaker( + // number of exceptions before breaking circuit + 5, + // time circuit opened before retry + TimeSpan.FromMinutes(1), + (exception, duration) => + { + // on circuit opened + _logger.LogTrace("Circuit breaker opened"); + }, + () => + { + // on circuit closed + _logger.LogTrace("Circuit breaker reset"); + }) + }; + } } } diff --git a/src/Web/WebMVC/Infrastructure/ResilientHttpClientFactory.cs b/src/Web/WebMVC/Infrastructure/ResilientHttpClientFactory.cs index 8efadf366..6aa108add 100644 --- a/src/Web/WebMVC/Infrastructure/ResilientHttpClientFactory.cs +++ b/src/Web/WebMVC/Infrastructure/ResilientHttpClientFactory.cs @@ -17,43 +17,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure =>_logger = logger; public ResilientHttpClient CreateResilientHttpClient() - => new ResilientHttpClient(CreatePolicies(), _logger); - - - private Policy[] CreatePolicies() - => new Policy[] - { - Policy.Handle() - .WaitAndRetryAsync( - // number of retries - 6, - // exponential backofff - retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - // on retry - (exception, timeSpan, retryCount, context) => - { - var msg = $"Retry {retryCount} implemented with Polly's RetryPolicy " + - $"of {context.PolicyKey} " + - $"at {context.ExecutionKey}, " + - $"due to: {exception}."; - _logger.LogWarning(msg); - _logger.LogDebug(msg); - }), - Policy.Handle() - .CircuitBreakerAsync( - // number of exceptions before breaking circuit - 5, - // time circuit opened before retry - TimeSpan.FromMinutes(1), - (exception, duration) => - { - // on circuit opened - _logger.LogTrace("Circuit breaker opened"); - }, - () => - { - // on circuit closed - _logger.LogTrace("Circuit breaker reset"); - })}; + => new ResilientHttpClient(_logger); } } diff --git a/src/Web/WebMVC/Startup.cs b/src/Web/WebMVC/Startup.cs index cd18e1c3a..ba0b1244f 100644 --- a/src/Web/WebMVC/Startup.cs +++ b/src/Web/WebMVC/Startup.cs @@ -74,7 +74,7 @@ namespace Microsoft.eShopOnContainers.WebMVC if (Configuration.GetValue("UseResilientHttp") == bool.TrueString) { - services.AddTransient(); + services.AddSingleton(); services.AddSingleton(sp => sp.GetService().CreateResilientHttpClient()); } else