136 lines
5.1 KiB
C#
136 lines
5.1 KiB
C#
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http.Policies;
|
|
using Microsoft.Extensions.Logging;
|
|
using Newtonsoft.Json;
|
|
using Polly;
|
|
using Polly.Wrap;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http
|
|
{
|
|
/// <summary>
|
|
/// HttpClient wrapper that integrates Retry and Circuit
|
|
/// breaker policies when invoking HTTP services.
|
|
/// Based on Polly library: https://github.com/App-vNext/Polly
|
|
/// </summary>
|
|
public class ResilientHttpClient : IHttpClient
|
|
{
|
|
private HttpClient _client;
|
|
private PolicyWrap _policyWrapper;
|
|
private ILogger<ResilientHttpClient> _logger;
|
|
public HttpClient Inst => _client;
|
|
|
|
public ResilientHttpClient(List<Policy> policies, ILogger<ResilientHttpClient> logger)
|
|
{
|
|
_client = new HttpClient();
|
|
_logger = logger;
|
|
|
|
// Add Policies to be applied
|
|
_policyWrapper = Policy.WrapAsync(policies.ToArray());
|
|
}
|
|
|
|
public ResilientHttpClient(List<ResiliencePolicy> policies, ILogger<ResilientHttpClient> logger)
|
|
{
|
|
_client = new HttpClient();
|
|
_logger = logger;
|
|
|
|
// Add Policies to be applied
|
|
_policyWrapper = Policy.WrapAsync(GeneratePolicies(policies));
|
|
}
|
|
|
|
private Policy[] GeneratePolicies(IList<ResiliencePolicy> policies)
|
|
{
|
|
var pollyPolicies = new List<Policy>();
|
|
|
|
foreach (var policy in policies)
|
|
{
|
|
switch (policy)
|
|
{
|
|
case RetryPolicy retryPolicy:
|
|
pollyPolicies.Add(
|
|
CreateRetryPolicy(
|
|
retryPolicy.Retries,
|
|
retryPolicy.BackoffSeconds,
|
|
retryPolicy.ExponentialBackoff));
|
|
break;
|
|
|
|
case CircuitBreakerPolicy circuitPolicy:
|
|
pollyPolicies.Add(
|
|
CreateCircuitBreakerPolicy(
|
|
circuitPolicy.ExceptionsAllowedBeforeBreaking,
|
|
circuitPolicy.DurationOfBreakInMinutes));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return pollyPolicies.ToArray();
|
|
}
|
|
|
|
private Policy CreateRetryPolicy(int retries, int backoffSeconds, bool exponentialBackoff) =>
|
|
Policy.Handle<HttpRequestException>()
|
|
.WaitAndRetryAsync(
|
|
retries,
|
|
retryAttempt => exponentialBackoff ? TimeSpan.FromSeconds(Math.Pow(backoffSeconds, retryAttempt)) : TimeSpan.FromSeconds(backoffSeconds),
|
|
(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);
|
|
}
|
|
);
|
|
|
|
private Policy CreateCircuitBreakerPolicy(int exceptionsAllowedBeforeBreaking, int durationOfBreakInMinutes) =>
|
|
Policy.Handle<HttpRequestException>()
|
|
.CircuitBreakerAsync(
|
|
exceptionsAllowedBeforeBreaking,
|
|
TimeSpan.FromMinutes(durationOfBreakInMinutes),
|
|
(exception, duration) =>
|
|
{
|
|
// on circuit opened
|
|
_logger.LogTrace("Circuit breaker opened");
|
|
},
|
|
() =>
|
|
{
|
|
// on circuit closed
|
|
_logger.LogTrace("Circuit breaker reset");
|
|
}
|
|
);
|
|
|
|
public Task<string> GetStringAsync(string uri) =>
|
|
HttpInvoker(() =>
|
|
_client.GetStringAsync(uri));
|
|
|
|
public Task<HttpResponseMessage> PostAsync<T>(string uri, T item) =>
|
|
// a new StringContent must be created for each retry
|
|
// as it is disposed after each call
|
|
HttpInvoker(() =>
|
|
{
|
|
var response = _client.PostAsync(uri, new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json"));
|
|
// raise exception if HttpResponseCode 500
|
|
// needed for circuit breaker to track fails
|
|
if (response.Result.StatusCode == HttpStatusCode.InternalServerError)
|
|
{
|
|
throw new HttpRequestException();
|
|
}
|
|
|
|
return response;
|
|
});
|
|
|
|
public Task<HttpResponseMessage> DeleteAsync(string uri) =>
|
|
HttpInvoker(() => _client.DeleteAsync(uri));
|
|
|
|
|
|
private Task<T> HttpInvoker<T>(Func<Task<T>> action) =>
|
|
// Executes the action applying all
|
|
// the policies defined in the wrapper
|
|
_policyWrapper.ExecuteAsync(() => action());
|
|
}
|
|
|
|
}
|