diff --git a/src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs b/src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs index a76e60f0b..88f6cb1d6 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; @@ -20,44 +21,56 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http public class ResilientHttpClient : IHttpClient { private HttpClient _client; - private readonly Dictionary _policiesPerOrigin; + private readonly ConcurrentDictionary _policiesPerOrigin; private ILogger _logger; private readonly Func> _policyCreator; - //public HttpClient Inst => _client; + public ResilientHttpClient(Func> policyCreator, ILogger logger) { _client = new HttpClient(); _logger = logger; - _policiesPerOrigin = new Dictionary(); + _policiesPerOrigin = new ConcurrentDictionary(); _policyCreator = policyCreator; } - private Task HttpInvoker(string origin, Func> action) + + public Task PostAsync(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") { - var normalizedOrigin = NormalizeOrigin(origin); + return DoPostPutAsync(HttpMethod.Post, uri, item, authorizationToken, requestId, authorizationMethod); + } - if (!_policiesPerOrigin.ContainsKey(normalizedOrigin)) + public Task PutAsync(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") + { + return DoPostPutAsync(HttpMethod.Put, uri, item, authorizationToken, requestId, authorizationMethod); + } + + public Task DeleteAsync(string uri, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") + { + var origin = GetOriginFromUri(uri); + + return HttpInvoker(origin, async () => { - var newWrapper = Policy.WrapAsync(_policyCreator(normalizedOrigin).ToArray()); - _policiesPerOrigin.Add(normalizedOrigin, newWrapper); - } + var requestMessage = new HttpRequestMessage(HttpMethod.Delete, uri); - var policyWrapper = _policiesPerOrigin[normalizedOrigin]; + if (authorizationToken != null) + { + requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken); + } - // Executes the action applying all - // the policies defined in the wrapper - return policyWrapper.ExecuteAsync(() => action()); - } + if (requestId != null) + { + requestMessage.Headers.Add("x-requestid", requestId); + } - private static string NormalizeOrigin(string origin) - { - return origin?.Trim()?.ToLower(); + return await _client.SendAsync(requestMessage); + }); } public Task GetStringAsync(string uri, string authorizationToken = null, string authorizationMethod = "Bearer") { var origin = GetOriginFromUri(uri); + return HttpInvoker(origin, async () => { var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri); @@ -73,13 +86,6 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http }); } - private static string GetOriginFromUri(string uri) - { - var url = new Uri(uri); - var origin = $"{url.Scheme}://{url.DnsSafeHost}:{url.Port}"; - return origin; - } - private Task DoPostPutAsync(HttpMethod method, string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") { if (method != HttpMethod.Post && method != HttpMethod.Put) @@ -90,6 +96,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http // a new StringContent must be created for each retry // as it is disposed after each call var origin = GetOriginFromUri(uri); + return HttpInvoker(origin, async () => { var requestMessage = new HttpRequestMessage(method, uri); @@ -120,34 +127,49 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http }); } - public Task PostAsync(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") - { - return DoPostPutAsync(HttpMethod.Post, uri, item, authorizationToken, requestId, authorizationMethod); - } - public Task PutAsync(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") + private Task HttpInvoker(string origin, Func> action) { - return DoPostPutAsync(HttpMethod.Put, uri, item, authorizationToken, requestId, authorizationMethod); + var policyWrapper = GetPolicyForOrigin(origin); + + if (policyWrapper != null) + { + // Executes the action applying all + // the policies defined in the wrapper + return policyWrapper.ExecuteAsync(() => action()); + } + else + { + throw new InvalidOperationException($"PolicyWrapper can't be created for origin {origin}"); + } } - public Task DeleteAsync(string uri, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") + + private PolicyWrap GetPolicyForOrigin(string origin) { - var origin = GetOriginFromUri(uri); - return HttpInvoker(origin, async () => + var normalizedOrigin = NormalizeOrigin(origin); + + if (!_policiesPerOrigin.TryGetValue(normalizedOrigin, out PolicyWrap policyWrapper)) { - var requestMessage = new HttpRequestMessage(HttpMethod.Delete, uri); + policyWrapper = Policy.WrapAsync(_policyCreator(normalizedOrigin) + .ToArray()); - if (authorizationToken != null) - { - requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken); - } + _policiesPerOrigin.TryAdd(normalizedOrigin, policyWrapper); + } - if (requestId != null) - { - requestMessage.Headers.Add("x-requestid", requestId); - } + return policyWrapper; + } - return await _client.SendAsync(requestMessage); - }); + private static string NormalizeOrigin(string origin) + { + return origin?.Trim()?.ToLower(); } + private static string GetOriginFromUri(string uri) + { + var url = new Uri(uri); + + var origin = $"{url.Scheme}://{url.DnsSafeHost}:{url.Port}"; + + return origin; + } } }