I wanted to use Polly
re-try and circuit breaker with Ocelot
api gateway. I am trying to wrap policies with DelegatingHandler
, the circuit breaker
works, but re-try
not works.
Below code just throw the exception, but NO re-try happening. When I am calling the API 3 times, circuit opens.
"ExceptionsAllowedBeforeBreaking": 3,
.CircuitBreakerAsync(route.QosOptions.ExceptionsAllowedBeforeBreaking,
[HttpGet("RaiseException")]
public async Task<int> RaiseException()
{
await Task.Delay(1);
throw new Exception("Mock Exception");
}
Custom Handler:
public class PollyWithInternalServerErrorCircuitBreakingDelegatingHandler : DelegatingHandler
{
private readonly IOcelotLogger _logger;
private readonly Polly.Wrap.AsyncPolicyWrap<HttpResponseMessage> _circuitBreakerPolicies;
public PollyWithInternalServerErrorCircuitBreakingDelegatingHandler(DownstreamRoute route, IOcelotLoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<PollyWithInternalServerErrorCircuitBreakingDelegatingHandler>();
var pollyQosProvider = new PollyQoSProvider(route, loggerFactory);
var retryPolicy = HttpPolicyExtensions.HandleTransientHttpError()
.OrResult(r => r.StatusCode == HttpStatusCode.NotFound)
.WaitAndRetryAsync(2, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
var responsePolicy = Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
.CircuitBreakerAsync(route.QosOptions.ExceptionsAllowedBeforeBreaking,
TimeSpan.FromMilliseconds(route.QosOptions.DurationOfBreak));
_circuitBreakerPolicies = Policy.WrapAsync(pollyQosProvider.CircuitBreaker.Policies)
.WrapAsync(retryPolicy).WrapAsync(responsePolicy);
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
return await _circuitBreakerPolicies.ExecuteAsync(() => base.SendAsync(request, cancellationToken));
}
catch (BrokenCircuitException ex)
{
_logger.LogError($"Reached to allowed number of exceptions. Circuit is open", ex);
throw;
}
catch (HttpRequestException ex)
{
_logger.LogError($"Error in CircuitBreakingDelegatingHandler.SendAsync", ex);
throw;
}
}
}
Ocelot Builder Extensions:
public static class OcelotBuilderExtensions
{
public static IOcelotBuilder AddPollyWithInternalServerErrorHandling(this IOcelotBuilder builder)
{
var errorMapping = new Dictionary<Type, Func<Exception, Error>>
{
{typeof(TaskCanceledException), e => new RequestTimedOutError(e)},
{typeof(TimeoutRejectedException), e => new RequestTimedOutError(e)},
{typeof(BrokenCircuitException), e => new RequestTimedOutError(e)}
};
builder.Services.AddSingleton(errorMapping);
DelegatingHandler QosDelegatingHandlerDelegate(DownstreamRoute route, IOcelotLoggerFactory logger)
{
return new PollyWithInternalServerErrorCircuitBreakingDelegatingHandler(route, logger);
}
builder.Services.AddSingleton((QosDelegatingHandlerDelegate)QosDelegatingHandlerDelegate);
return builder;
}
}
Program.cs
var builder = WebApplication.CreateBuilder(args);
//Ocelot add it's configuration file
builder.Configuration.AddJsonFile($"ocelot.config.{builder.Environment.EnvironmentName}.json", optional: false, reloadOnChange: true);
builder.Services.AddOcelot(builder.Configuration)
.AddPollyWithInternalServerErrorHandling();
Ocelot Configuration
"UpstreamHttpMethod": [ "GET" ],
"QoSOptions": {
//Number of exceptions which are allowed before the circuit breaker is triggered.
"ExceptionsAllowedBeforeBreaking": 3,
//Duration in milliseconds for which the circuit breaker would remain open after been tripped
"DurationOfBreak": 5000,
//Duration after which the request is considered as timedout
"TimeoutValue": 100000
}