mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-06-19 12:38:15 +08:00
Feature/move polly (#561)
* added delegate to select last handler * #529 implemented a way we can inject the last delegating handler * wip - moving code * #529 removed loads of qos code and moved it into Ocelot.Provider.Polly
This commit is contained in:
@ -1,32 +1,33 @@
|
||||
using Polly.Timeout;
|
||||
|
||||
namespace Ocelot.Configuration
|
||||
{
|
||||
public class QoSOptions
|
||||
{
|
||||
public QoSOptions(
|
||||
int exceptionsAllowedBeforeBreaking,
|
||||
int durationofBreak,
|
||||
int timeoutValue,
|
||||
string key,
|
||||
TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic)
|
||||
{
|
||||
ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
|
||||
DurationOfBreak = durationofBreak;
|
||||
TimeoutValue = timeoutValue;
|
||||
TimeoutStrategy = timeoutStrategy;
|
||||
Key = key;
|
||||
}
|
||||
|
||||
public int ExceptionsAllowedBeforeBreaking { get; }
|
||||
|
||||
public int DurationOfBreak { get; }
|
||||
|
||||
public int TimeoutValue { get; }
|
||||
|
||||
public TimeoutStrategy TimeoutStrategy { get; }
|
||||
|
||||
public bool UseQos => ExceptionsAllowedBeforeBreaking > 0 && TimeoutValue > 0;
|
||||
public string Key { get; }
|
||||
}
|
||||
}
|
||||
namespace Ocelot.Configuration
|
||||
{
|
||||
public class QoSOptions
|
||||
{
|
||||
public QoSOptions(
|
||||
int exceptionsAllowedBeforeBreaking,
|
||||
int durationofBreak,
|
||||
int timeoutValue,
|
||||
string key,
|
||||
//todo - this is never set in Ocelot so always Pessimistic...I guess it doesn't
|
||||
//matter to much.
|
||||
string timeoutStrategy = "Pessimistic")
|
||||
{
|
||||
ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
|
||||
DurationOfBreak = durationofBreak;
|
||||
TimeoutValue = timeoutValue;
|
||||
TimeoutStrategy = timeoutStrategy;
|
||||
Key = key;
|
||||
}
|
||||
|
||||
public int ExceptionsAllowedBeforeBreaking { get; }
|
||||
|
||||
public int DurationOfBreak { get; }
|
||||
|
||||
public int TimeoutValue { get; }
|
||||
|
||||
public string TimeoutStrategy { get; }
|
||||
|
||||
public bool UseQos => ExceptionsAllowedBeforeBreaking > 0 && TimeoutValue > 0;
|
||||
|
||||
public string Key { get; }
|
||||
}
|
||||
}
|
||||
|
@ -28,17 +28,12 @@ namespace Ocelot.DependencyInjection
|
||||
using Ocelot.Requester.QoS;
|
||||
using Ocelot.Responder;
|
||||
using Ocelot.ServiceDiscovery;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Ocelot.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using System.Net.Http;
|
||||
using Ocelot.Infrastructure;
|
||||
using Ocelot.Middleware.Multiplexer;
|
||||
using ServiceDiscovery.Providers;
|
||||
using Ocelot.Request.Creator;
|
||||
|
||||
public class OcelotBuilder : IOcelotBuilder
|
||||
@ -75,8 +70,6 @@ namespace Ocelot.DependencyInjection
|
||||
Services.TryAddSingleton<IRegionCreator, RegionCreator>();
|
||||
Services.TryAddSingleton<IFileConfigurationRepository, DiskFileConfigurationRepository>();
|
||||
Services.TryAddSingleton<IFileConfigurationSetter, FileAndInternalConfigurationSetter>();
|
||||
Services.TryAddSingleton<IQosProviderHouse, QosProviderHouse>();
|
||||
Services.TryAddSingleton<IQoSProviderFactory, QoSProviderFactory>();
|
||||
Services.TryAddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>();
|
||||
Services.TryAddSingleton<ILoadBalancerFactory, LoadBalancerFactory>();
|
||||
Services.TryAddSingleton<ILoadBalancerHouse, LoadBalancerHouse>();
|
||||
@ -136,6 +129,7 @@ namespace Ocelot.DependencyInjection
|
||||
Services.TryAddSingleton<IDefinedAggregatorProvider, ServiceLocatorDefinedAggregatorProvider>();
|
||||
Services.TryAddSingleton<IDownstreamRequestCreator, DownstreamRequestCreator>();
|
||||
Services.TryAddSingleton<IFrameworkDescription, FrameworkDescription>();
|
||||
Services.TryAddSingleton<IQoSFactory, QoSFactory>();
|
||||
}
|
||||
|
||||
public IOcelotBuilder AddSingletonDefinedAggregator<T>()
|
||||
|
@ -45,6 +45,5 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.0" />
|
||||
<PackageReference Include="Polly" Version="6.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -1,31 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Requester.QoS;
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.Requester
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Responses;
|
||||
using QoS;
|
||||
|
||||
public class DelegatingHandlerHandlerFactory : IDelegatingHandlerHandlerFactory
|
||||
{
|
||||
private readonly ITracingHandlerFactory _factory;
|
||||
private readonly IOcelotLoggerFactory _loggerFactory;
|
||||
private readonly IQosProviderHouse _qosProviderHouse;
|
||||
private readonly ITracingHandlerFactory _tracingFactory;
|
||||
private readonly IQoSFactory _qoSFactory;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public DelegatingHandlerHandlerFactory(IOcelotLoggerFactory loggerFactory,
|
||||
ITracingHandlerFactory factory,
|
||||
IQosProviderHouse qosProviderHouse,
|
||||
public DelegatingHandlerHandlerFactory(
|
||||
ITracingHandlerFactory tracingFactory,
|
||||
IQoSFactory qoSFactory,
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_factory = factory;
|
||||
_loggerFactory = loggerFactory;
|
||||
_qosProviderHouse = qosProviderHouse;
|
||||
_tracingFactory = tracingFactory;
|
||||
_qoSFactory = qoSFactory;
|
||||
}
|
||||
|
||||
public Response<List<Func<DelegatingHandler>>> Get(DownstreamReRoute request)
|
||||
@ -64,19 +61,21 @@ namespace Ocelot.Requester
|
||||
|
||||
if (request.HttpHandlerOptions.UseTracing)
|
||||
{
|
||||
handlers.Add(() => (DelegatingHandler)_factory.Get());
|
||||
handlers.Add(() => (DelegatingHandler)_tracingFactory.Get());
|
||||
}
|
||||
|
||||
if (request.QosOptions.UseQos)
|
||||
{
|
||||
var qosProvider = _qosProviderHouse.Get(request);
|
||||
var handler = _qoSFactory.Get(request);
|
||||
|
||||
if (qosProvider.IsError)
|
||||
if (handler != null && !handler.IsError)
|
||||
{
|
||||
return new ErrorResponse<List<Func<DelegatingHandler>>>(qosProvider.Errors);
|
||||
handlers.Add(() => handler.Data);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ErrorResponse<List<Func<DelegatingHandler>>>(handler?.Errors);
|
||||
}
|
||||
|
||||
handlers.Add(() => new PollyCircuitBreakingDelegatingHandler(qosProvider.Data, _loggerFactory));
|
||||
}
|
||||
|
||||
return new OkResponse<List<Func<DelegatingHandler>>>(handlers);
|
||||
|
@ -132,7 +132,7 @@ namespace Ocelot.Requester
|
||||
{
|
||||
var cacheKey = $"{request.DownstreamRequest.Method}:{request.DownstreamRequest.OriginalString}";
|
||||
|
||||
this._logger.LogDebug($"Cache key for request is {cacheKey}");
|
||||
_logger.LogDebug($"Cache key for request is {cacheKey}");
|
||||
|
||||
return cacheKey;
|
||||
}
|
||||
|
@ -4,8 +4,6 @@ using System.Threading.Tasks;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
using Ocelot.Responses;
|
||||
using Polly.CircuitBreaker;
|
||||
using Polly.Timeout;
|
||||
|
||||
namespace Ocelot.Requester
|
||||
{
|
||||
@ -35,18 +33,6 @@ namespace Ocelot.Requester
|
||||
var response = await httpClient.SendAsync(context.DownstreamRequest.ToHttpRequestMessage());
|
||||
return new OkResponse<HttpResponseMessage>(response);
|
||||
}
|
||||
catch (TimeoutRejectedException exception)
|
||||
{
|
||||
return new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
|
||||
}
|
||||
catch (TaskCanceledException exception)
|
||||
{
|
||||
return new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
|
||||
}
|
||||
catch (BrokenCircuitException exception)
|
||||
{
|
||||
return new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
return new ErrorResponse<HttpResponseMessage>(new UnableToCompleteRequestError(exception));
|
||||
|
@ -1,45 +0,0 @@
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Requester.QoS;
|
||||
using Polly;
|
||||
using Polly.CircuitBreaker;
|
||||
using Polly.Timeout;
|
||||
|
||||
namespace Ocelot.Requester
|
||||
{
|
||||
public class PollyCircuitBreakingDelegatingHandler : DelegatingHandler
|
||||
{
|
||||
private readonly IQoSProvider _qoSProvider;
|
||||
private readonly IOcelotLogger _logger;
|
||||
|
||||
public PollyCircuitBreakingDelegatingHandler(
|
||||
IQoSProvider qoSProvider,
|
||||
IOcelotLoggerFactory loggerFactory)
|
||||
{
|
||||
_qoSProvider = qoSProvider;
|
||||
_logger = loggerFactory.CreateLogger<PollyCircuitBreakingDelegatingHandler>();
|
||||
}
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Policy
|
||||
.WrapAsync(_qoSProvider.CircuitBreaker.CircuitBreakerPolicy, _qoSProvider.CircuitBreaker.TimeoutPolicy)
|
||||
.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.SendAync", ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
using Polly.CircuitBreaker;
|
||||
using Polly.Timeout;
|
||||
|
||||
namespace Ocelot.Requester.QoS
|
||||
{
|
||||
public class CircuitBreaker
|
||||
{
|
||||
public CircuitBreaker(CircuitBreakerPolicy circuitBreakerPolicy, TimeoutPolicy timeoutPolicy)
|
||||
{
|
||||
CircuitBreakerPolicy = circuitBreakerPolicy;
|
||||
TimeoutPolicy = timeoutPolicy;
|
||||
}
|
||||
|
||||
public CircuitBreakerPolicy CircuitBreakerPolicy { get; private set; }
|
||||
public TimeoutPolicy TimeoutPolicy { get; private set; }
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
namespace Ocelot.Requester.QoS
|
||||
{
|
||||
public interface IQoSProvider
|
||||
{
|
||||
CircuitBreaker CircuitBreaker { get; }
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.LoadBalancer.LoadBalancers;
|
||||
|
||||
namespace Ocelot.Requester.QoS
|
||||
{
|
||||
public interface IQoSProviderFactory
|
||||
{
|
||||
IQoSProvider Get(DownstreamReRoute reRoute);
|
||||
}
|
||||
}
|
11
src/Ocelot/Requester/QoS/IQosFactory.cs
Normal file
11
src/Ocelot/Requester/QoS/IQosFactory.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Ocelot.Requester.QoS
|
||||
{
|
||||
using System.Net.Http;
|
||||
using Configuration;
|
||||
using Responses;
|
||||
|
||||
public interface IQoSFactory
|
||||
{
|
||||
Response<DelegatingHandler> Get(DownstreamReRoute request);
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.Requester.QoS
|
||||
{
|
||||
public interface IQosProviderHouse
|
||||
{
|
||||
Response<IQoSProvider> Get(DownstreamReRoute reRoute);
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
namespace Ocelot.Requester.QoS
|
||||
{
|
||||
public class NoQoSProvider : IQoSProvider
|
||||
{
|
||||
public CircuitBreaker CircuitBreaker { get; }
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Logging;
|
||||
using Polly;
|
||||
using Polly.CircuitBreaker;
|
||||
using Polly.Timeout;
|
||||
|
||||
namespace Ocelot.Requester.QoS
|
||||
{
|
||||
public class PollyQoSProvider : IQoSProvider
|
||||
{
|
||||
private readonly CircuitBreakerPolicy _circuitBreakerPolicy;
|
||||
private readonly TimeoutPolicy _timeoutPolicy;
|
||||
private readonly IOcelotLogger _logger;
|
||||
private readonly CircuitBreaker _circuitBreaker;
|
||||
|
||||
public PollyQoSProvider(DownstreamReRoute reRoute, IOcelotLoggerFactory loggerFactory)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger<PollyQoSProvider>();
|
||||
|
||||
_timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(reRoute.QosOptions.TimeoutValue), reRoute.QosOptions.TimeoutStrategy);
|
||||
|
||||
_circuitBreakerPolicy = Policy
|
||||
.Handle<HttpRequestException>()
|
||||
.Or<TimeoutRejectedException>()
|
||||
.Or<TimeoutException>()
|
||||
.CircuitBreakerAsync(
|
||||
exceptionsAllowedBeforeBreaking: reRoute.QosOptions.ExceptionsAllowedBeforeBreaking,
|
||||
durationOfBreak: TimeSpan.FromMilliseconds(reRoute.QosOptions.DurationOfBreak),
|
||||
onBreak: (ex, breakDelay) =>
|
||||
{
|
||||
_logger.LogError(
|
||||
".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!", ex);
|
||||
},
|
||||
onReset: () =>
|
||||
{
|
||||
_logger.LogDebug(".Breaker logging: Call ok! Closed the circuit again.");
|
||||
},
|
||||
onHalfOpen: () =>
|
||||
{
|
||||
_logger.LogDebug(".Breaker logging: Half-open; next call is a trial.");
|
||||
}
|
||||
);
|
||||
|
||||
_circuitBreaker = new CircuitBreaker(_circuitBreakerPolicy, _timeoutPolicy);
|
||||
}
|
||||
|
||||
public CircuitBreaker CircuitBreaker => _circuitBreaker;
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Logging;
|
||||
|
||||
namespace Ocelot.Requester.QoS
|
||||
{
|
||||
public class QoSProviderFactory : IQoSProviderFactory
|
||||
{
|
||||
private readonly IOcelotLoggerFactory _loggerFactory;
|
||||
|
||||
public QoSProviderFactory(IOcelotLoggerFactory loggerFactory)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
}
|
||||
|
||||
public IQoSProvider Get(DownstreamReRoute reRoute)
|
||||
{
|
||||
if (reRoute.QosOptions.UseQos)
|
||||
{
|
||||
return new PollyQoSProvider(reRoute, _loggerFactory);
|
||||
}
|
||||
|
||||
return new NoQoSProvider();
|
||||
}
|
||||
}
|
||||
}
|
33
src/Ocelot/Requester/QoS/QosFactory.cs
Normal file
33
src/Ocelot/Requester/QoS/QosFactory.cs
Normal file
@ -0,0 +1,33 @@
|
||||
namespace Ocelot.Requester.QoS
|
||||
{
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using Configuration;
|
||||
using Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Responses;
|
||||
|
||||
public class QoSFactory : IQoSFactory
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IOcelotLoggerFactory _ocelotLoggerFactory;
|
||||
|
||||
public QoSFactory(IServiceProvider serviceProvider, IOcelotLoggerFactory ocelotLoggerFactory)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_ocelotLoggerFactory = ocelotLoggerFactory;
|
||||
}
|
||||
|
||||
public Response<DelegatingHandler> Get(DownstreamReRoute request)
|
||||
{
|
||||
var handler = _serviceProvider.GetService<QosDelegatingHandlerDelegate>();
|
||||
|
||||
if (handler != null)
|
||||
{
|
||||
return new OkResponse<DelegatingHandler>(handler(request, _ocelotLoggerFactory));
|
||||
}
|
||||
|
||||
return new ErrorResponse<DelegatingHandler>(new UnableToFindQoSProviderError($"could not find qosProvider for {request.DownstreamScheme}{request.DownstreamAddresses}{request.DownstreamPathTemplate}"));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.Requester.QoS
|
||||
{
|
||||
public class QosProviderHouse : IQosProviderHouse
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, IQoSProvider> _qoSProviders;
|
||||
private readonly IQoSProviderFactory _qoSProviderFactory;
|
||||
|
||||
public QosProviderHouse(IQoSProviderFactory qoSProviderFactory)
|
||||
{
|
||||
_qoSProviderFactory = qoSProviderFactory;
|
||||
_qoSProviders = new ConcurrentDictionary<string, IQoSProvider>();
|
||||
}
|
||||
|
||||
public Response<IQoSProvider> Get(DownstreamReRoute reRoute)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_qoSProviders.TryGetValue(reRoute.QosOptions.Key, out var qosProvider))
|
||||
{
|
||||
if (reRoute.QosOptions.UseQos && qosProvider.CircuitBreaker == null)
|
||||
{
|
||||
qosProvider = _qoSProviderFactory.Get(reRoute);
|
||||
Add(reRoute.QosOptions.Key, qosProvider);
|
||||
}
|
||||
|
||||
return new OkResponse<IQoSProvider>(_qoSProviders[reRoute.QosOptions.Key]);
|
||||
}
|
||||
|
||||
qosProvider = _qoSProviderFactory.Get(reRoute);
|
||||
Add(reRoute.QosOptions.Key, qosProvider);
|
||||
return new OkResponse<IQoSProvider>(qosProvider);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new ErrorResponse<IQoSProvider>(new List<Ocelot.Errors.Error>()
|
||||
{
|
||||
new UnableToFindQoSProviderError($"unabe to find qos provider for {reRoute.QosOptions.Key}, exception was {ex}")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void Add(string key, IQoSProvider qosProvider)
|
||||
{
|
||||
_qoSProviders.AddOrUpdate(key, qosProvider, (x, y) => qosProvider);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Ocelot.Errors;
|
||||
|
||||
namespace Ocelot.Requester.QoS
|
||||
namespace Ocelot.Requester.QoS
|
||||
{
|
||||
using Ocelot.Errors;
|
||||
|
||||
public class UnableToFindQoSProviderError : Error
|
||||
{
|
||||
public UnableToFindQoSProviderError(string message)
|
||||
|
8
src/Ocelot/Requester/QosDelegatingHandlerDelegate.cs
Normal file
8
src/Ocelot/Requester/QosDelegatingHandlerDelegate.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Ocelot.Requester
|
||||
{
|
||||
using System.Net.Http;
|
||||
using Configuration;
|
||||
using Logging;
|
||||
|
||||
public delegate DelegatingHandler QosDelegatingHandlerDelegate(DownstreamReRoute reRoute, IOcelotLoggerFactory logger);
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using Ocelot.Errors;
|
||||
|
||||
namespace Ocelot.Requester
|
||||
{
|
||||
public class RequestTimedOutError : Error
|
||||
{
|
||||
public RequestTimedOutError(Exception exception)
|
||||
: base($"Timeout making http request, exception: {exception}", OcelotErrorCode.RequestTimedOutError)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user