mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-11-01 05:55:27 +08:00 
			
		
		
		
	Merge pull request #39 from TomPallister/feature/qos-documentation
Feature/qos documentation
This commit is contained in:
		
							
								
								
									
										40
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								README.md
									
									
									
									
									
								
							| @@ -305,19 +305,25 @@ Below is an example configuration that will transforms claims to query string pa | |||||||
| This shows a transform where Ocelot looks at the users LocationId claim and add its as | This shows a transform where Ocelot looks at the users LocationId claim and add its as | ||||||
| a query string parameter to be forwarded onto the downstream service. | a query string parameter to be forwarded onto the downstream service. | ||||||
|  |  | ||||||
| ## Logging | ## Quality of Service | ||||||
|  |  | ||||||
| Ocelot uses the standard logging interfaces ILoggerFactory / ILogger<T> at the moment.  | Ocelot supports one QoS capability at the current time. You can set on a per ReRoute basis if you  | ||||||
| This is encapsulated in  IOcelotLogger / IOcelotLoggerFactory with an implementation  | want to use a circuit breaker when making requests to a downstream service. This uses the an awesome | ||||||
| for the standard asp.net core logging stuff at the moment.  | .NET library called Polly check them out [here](https://github.com/App-vNext/Polly). | ||||||
|  |  | ||||||
| There are a bunch of debugging logs in the ocelot middlewares however I think the  | Add the following section to a ReRoute configuration.  | ||||||
| system probably needs more logging in the code it calls into. Other than the debugging |  | ||||||
| there is a global error handler that should catch any errors thrown and log them as errors. |  | ||||||
|  |  | ||||||
| The reason for not just using bog standard framework logging is that I could not  | 		"QoSOptions": { | ||||||
| work out how to override the request id that get's logged when setting IncludeScopes  | 			"ExceptionsAllowedBeforeBreaking":3, | ||||||
| to true for logging settings. Nicely onto the next feature. | 			"DurationOfBreak":5, | ||||||
|  | 			"TimeoutValue":5000 | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | You must set a number greater than 0 against ExceptionsAllowedBeforeBreaking for this rule to be  | ||||||
|  | implemented. Duration of break is how long the circuit breaker will stay open for after it is tripped. | ||||||
|  | TimeoutValue means ff a request takes more than 5 seconds it will automatically be timed out.  | ||||||
|  |  | ||||||
|  | If you do not add a QoS section QoS will not be used. | ||||||
|  |  | ||||||
| ## RequestId / CorrelationId | ## RequestId / CorrelationId | ||||||
|  |  | ||||||
| @@ -404,6 +410,20 @@ http request before it is passed to Ocelots request creator. | |||||||
| Obviously you can just add middleware as normal before the call to app.UseOcelot() It cannot be added | Obviously you can just add middleware as normal before the call to app.UseOcelot() It cannot be added | ||||||
| after as Ocelot does not call the next middleware. | after as Ocelot does not call the next middleware. | ||||||
|  |  | ||||||
|  | ## Logging | ||||||
|  |  | ||||||
|  | Ocelot uses the standard logging interfaces ILoggerFactory / ILogger<T> at the moment.  | ||||||
|  | This is encapsulated in  IOcelotLogger / IOcelotLoggerFactory with an implementation  | ||||||
|  | for the standard asp.net core logging stuff at the moment.  | ||||||
|  |  | ||||||
|  | There are a bunch of debugging logs in the ocelot middlewares however I think the  | ||||||
|  | system probably needs more logging in the code it calls into. Other than the debugging | ||||||
|  | there is a global error handler that should catch any errors thrown and log them as errors. | ||||||
|  |  | ||||||
|  | The reason for not just using bog standard framework logging is that I could not  | ||||||
|  | work out how to override the request id that get's logged when setting IncludeScopes  | ||||||
|  | to true for logging settings. Nicely onto the next feature. | ||||||
|  |  | ||||||
| ## Not supported | ## Not supported | ||||||
|  |  | ||||||
| Ocelot does not support... | Ocelot does not support... | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | namespace Ocelot.Configuration.Builder | ||||||
|  | { | ||||||
|  |     public class QoSOptionsBuilder | ||||||
|  |     { | ||||||
|  |         private int _exceptionsAllowedBeforeBreaking; | ||||||
|  |  | ||||||
|  |         private int _durationOfBreak; | ||||||
|  |  | ||||||
|  |         private int _timeoutValue; | ||||||
|  |  | ||||||
|  |         public QoSOptionsBuilder WithExceptionsAllowedBeforeBreaking(int exceptionsAllowedBeforeBreaking) | ||||||
|  |         { | ||||||
|  |             _exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public QoSOptionsBuilder WithDurationOfBreak(int durationOfBreak) | ||||||
|  |         { | ||||||
|  |             _durationOfBreak = durationOfBreak; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public QoSOptionsBuilder WithTimeoutValue(int timeoutValue) | ||||||
|  |         { | ||||||
|  |             _timeoutValue = timeoutValue; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public QoSOptions Build() | ||||||
|  |         { | ||||||
|  |             return new QoSOptions(_exceptionsAllowedBeforeBreaking, _durationOfBreak, _timeoutValue); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -22,7 +22,6 @@ namespace Ocelot.Configuration.Builder | |||||||
|         private string _requestIdHeaderKey; |         private string _requestIdHeaderKey; | ||||||
|         private bool _isCached; |         private bool _isCached; | ||||||
|         private CacheOptions _fileCacheOptions; |         private CacheOptions _fileCacheOptions; | ||||||
|         private string _serviceName; |  | ||||||
|         private string _downstreamScheme; |         private string _downstreamScheme; | ||||||
|         private string _downstreamHost; |         private string _downstreamHost; | ||||||
|         private int _downstreamPort; |         private int _downstreamPort; | ||||||
| @@ -49,12 +48,6 @@ namespace Ocelot.Configuration.Builder | |||||||
|             return this; |             return this; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public ReRouteBuilder WithServiceName(string serviceName) |  | ||||||
|         { |  | ||||||
|             _serviceName = serviceName; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public ReRouteBuilder WithDownstreamPathTemplate(string input) |         public ReRouteBuilder WithDownstreamPathTemplate(string input) | ||||||
|         { |         { | ||||||
|             _downstreamPathTemplate = input; |             _downstreamPathTemplate = input; | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| using System; | using System; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.Net.Http; |  | ||||||
| using System.Text; | using System.Text; | ||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
| using Microsoft.Extensions.Logging; | using Microsoft.Extensions.Logging; | ||||||
| @@ -10,9 +9,9 @@ using Ocelot.Configuration.File; | |||||||
| using Ocelot.Configuration.Parser; | using Ocelot.Configuration.Parser; | ||||||
| using Ocelot.Configuration.Validator; | using Ocelot.Configuration.Validator; | ||||||
| using Ocelot.LoadBalancer.LoadBalancers; | using Ocelot.LoadBalancer.LoadBalancers; | ||||||
|  | using Ocelot.Requester.QoS; | ||||||
| using Ocelot.Responses; | using Ocelot.Responses; | ||||||
| using Ocelot.Utilities; | using Ocelot.Utilities; | ||||||
| using Ocelot.Values; |  | ||||||
|  |  | ||||||
| namespace Ocelot.Configuration.Creator | namespace Ocelot.Configuration.Creator | ||||||
| { | { | ||||||
| @@ -26,11 +25,14 @@ namespace Ocelot.Configuration.Creator | |||||||
|         private const string RegExMatchEverything = ".*"; |         private const string RegExMatchEverything = ".*"; | ||||||
|         private const string RegExMatchEndString = "$"; |         private const string RegExMatchEndString = "$"; | ||||||
|         private const string RegExIgnoreCase = "(?i)"; |         private const string RegExIgnoreCase = "(?i)"; | ||||||
|  |         private const string RegExForwardSlashOnly = "^/$"; | ||||||
|  |  | ||||||
|         private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser; |         private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser; | ||||||
|         private readonly ILogger<FileOcelotConfigurationCreator> _logger; |         private readonly ILogger<FileOcelotConfigurationCreator> _logger; | ||||||
|         private readonly ILoadBalancerFactory _loadBalanceFactory; |         private readonly ILoadBalancerFactory _loadBalanceFactory; | ||||||
|         private readonly ILoadBalancerHouse _loadBalancerHouse; |         private readonly ILoadBalancerHouse _loadBalancerHouse; | ||||||
|  |         private readonly IQoSProviderFactory _qoSProviderFactory; | ||||||
|  |         private readonly IQosProviderHouse _qosProviderHouse; | ||||||
|  |  | ||||||
|         public FileOcelotConfigurationCreator( |         public FileOcelotConfigurationCreator( | ||||||
|             IOptions<FileConfiguration> options,  |             IOptions<FileConfiguration> options,  | ||||||
| @@ -38,10 +40,14 @@ namespace Ocelot.Configuration.Creator | |||||||
|             IClaimToThingConfigurationParser claimToThingConfigurationParser,  |             IClaimToThingConfigurationParser claimToThingConfigurationParser,  | ||||||
|             ILogger<FileOcelotConfigurationCreator> logger, |             ILogger<FileOcelotConfigurationCreator> logger, | ||||||
|             ILoadBalancerFactory loadBalancerFactory, |             ILoadBalancerFactory loadBalancerFactory, | ||||||
|             ILoadBalancerHouse loadBalancerHouse) |             ILoadBalancerHouse loadBalancerHouse,  | ||||||
|  |             IQoSProviderFactory qoSProviderFactory,  | ||||||
|  |             IQosProviderHouse qosProviderHouse) | ||||||
|         { |         { | ||||||
|             _loadBalanceFactory = loadBalancerFactory; |             _loadBalanceFactory = loadBalancerFactory; | ||||||
|             _loadBalancerHouse = loadBalancerHouse; |             _loadBalancerHouse = loadBalancerHouse; | ||||||
|  |             _qoSProviderFactory = qoSProviderFactory; | ||||||
|  |             _qosProviderHouse = qosProviderHouse; | ||||||
|             _options = options; |             _options = options; | ||||||
|             _configurationValidator = configurationValidator; |             _configurationValidator = configurationValidator; | ||||||
|             _claimToThingConfigurationParser = claimToThingConfigurationParser; |             _claimToThingConfigurationParser = claimToThingConfigurationParser; | ||||||
| @@ -86,16 +92,17 @@ namespace Ocelot.Configuration.Creator | |||||||
|         { |         { | ||||||
|             var isAuthenticated = IsAuthenticated(fileReRoute); |             var isAuthenticated = IsAuthenticated(fileReRoute); | ||||||
|  |  | ||||||
|             var isAuthorised = IsAuthenticated(fileReRoute); |             var isAuthorised = IsAuthorised(fileReRoute); | ||||||
|  |  | ||||||
|             var isCached = IsCached(fileReRoute); |             var isCached = IsCached(fileReRoute); | ||||||
|  |  | ||||||
|             var requestIdKey = BuildRequestId(fileReRoute, globalConfiguration); |             var requestIdKey = BuildRequestId(fileReRoute, globalConfiguration); | ||||||
|  |  | ||||||
|             var loadBalancerKey = BuildLoadBalancerKey(fileReRoute); |             var reRouteKey = BuildReRouteKey(fileReRoute); | ||||||
|  |  | ||||||
|             var upstreamTemplatePattern = BuildUpstreamTemplate(fileReRoute); |             var upstreamTemplatePattern = BuildUpstreamTemplatePattern(fileReRoute); | ||||||
|             var isQos = fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions.TimeoutValue >0; |  | ||||||
|  |             var isQos = IsQoS(fileReRoute); | ||||||
|  |  | ||||||
|             var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration); |             var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration); | ||||||
|  |  | ||||||
| @@ -103,11 +110,12 @@ namespace Ocelot.Configuration.Creator | |||||||
|  |  | ||||||
|             var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest); |             var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest); | ||||||
|   |   | ||||||
|  |  | ||||||
|             var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest); |             var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest); | ||||||
|  |  | ||||||
|             var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest); |             var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest); | ||||||
|  |  | ||||||
|  |             var qosOptions = BuildQoSOptions(fileReRoute); | ||||||
|  |  | ||||||
|             var reRoute = new ReRouteBuilder() |             var reRoute = new ReRouteBuilder() | ||||||
|                 .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) |                 .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) | ||||||
|                 .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) |                 .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) | ||||||
| @@ -127,14 +135,31 @@ namespace Ocelot.Configuration.Creator | |||||||
|                 .WithLoadBalancer(fileReRoute.LoadBalancer) |                 .WithLoadBalancer(fileReRoute.LoadBalancer) | ||||||
|                 .WithDownstreamHost(fileReRoute.DownstreamHost) |                 .WithDownstreamHost(fileReRoute.DownstreamHost) | ||||||
|                 .WithDownstreamPort(fileReRoute.DownstreamPort) |                 .WithDownstreamPort(fileReRoute.DownstreamPort) | ||||||
|                 .WithLoadBalancerKey(loadBalancerKey) |                 .WithLoadBalancerKey(reRouteKey) | ||||||
|                 .WithServiceProviderConfiguraion(serviceProviderConfiguration) |                 .WithServiceProviderConfiguraion(serviceProviderConfiguration) | ||||||
|  |                 .WithIsQos(isQos) | ||||||
|  |                 .WithQosOptions(qosOptions) | ||||||
|                 .Build();    |                 .Build();    | ||||||
|  |  | ||||||
|             await SetupLoadBalancer(reRoute); |             await SetupLoadBalancer(reRoute); | ||||||
|  |             SetupQosProvider(reRoute); | ||||||
|             return reRoute; |             return reRoute; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         private QoSOptions BuildQoSOptions(FileReRoute fileReRoute) | ||||||
|  |         { | ||||||
|  |             return new QoSOptionsBuilder() | ||||||
|  |                 .WithExceptionsAllowedBeforeBreaking(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking) | ||||||
|  |                 .WithDurationOfBreak(fileReRoute.QoSOptions.DurationOfBreak) | ||||||
|  |                 .WithTimeoutValue(fileReRoute.QoSOptions.TimeoutValue) | ||||||
|  |                 .Build(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private bool IsQoS(FileReRoute fileReRoute) | ||||||
|  |         { | ||||||
|  |             return fileReRoute.QoSOptions?.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions?.TimeoutValue > 0; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         private bool IsAuthenticated(FileReRoute fileReRoute) |         private bool IsAuthenticated(FileReRoute fileReRoute) | ||||||
|         { |         { | ||||||
|             return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); |             return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); | ||||||
| @@ -161,7 +186,7 @@ namespace Ocelot.Configuration.Creator | |||||||
|                 return requestIdKey; |                 return requestIdKey; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private string BuildLoadBalancerKey(FileReRoute fileReRoute) |         private string BuildReRouteKey(FileReRoute fileReRoute) | ||||||
|         { |         { | ||||||
|             //note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain |             //note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain | ||||||
|             var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}{fileReRoute.UpstreamHttpMethod}"; |             var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}{fileReRoute.UpstreamHttpMethod}"; | ||||||
| @@ -183,7 +208,13 @@ namespace Ocelot.Configuration.Creator | |||||||
|         private async Task SetupLoadBalancer(ReRoute reRoute) |         private async Task SetupLoadBalancer(ReRoute reRoute) | ||||||
|         { |         { | ||||||
|             var loadBalancer = await _loadBalanceFactory.Get(reRoute); |             var loadBalancer = await _loadBalanceFactory.Get(reRoute); | ||||||
|             _loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer); |             _loadBalancerHouse.Add(reRoute.ReRouteKey, loadBalancer); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void SetupQosProvider(ReRoute reRoute) | ||||||
|  |         { | ||||||
|  |             var loadBalancer = _qoSProviderFactory.Get(reRoute); | ||||||
|  |             _qosProviderHouse.Add(reRoute.ReRouteKey, loadBalancer); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private ServiceProviderConfiguraion BuildServiceProviderConfiguration(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) |         private ServiceProviderConfiguraion BuildServiceProviderConfiguration(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) | ||||||
| @@ -204,7 +235,7 @@ namespace Ocelot.Configuration.Creator | |||||||
|                     .Build(); |                     .Build(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private string BuildUpstreamTemplate(FileReRoute reRoute) |         private string BuildUpstreamTemplatePattern(FileReRoute reRoute) | ||||||
|         { |         { | ||||||
|             var upstreamTemplate = reRoute.UpstreamPathTemplate; |             var upstreamTemplate = reRoute.UpstreamPathTemplate; | ||||||
|  |  | ||||||
| @@ -228,6 +259,11 @@ namespace Ocelot.Configuration.Creator | |||||||
|                 upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything); |                 upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             if (upstreamTemplate == "/") | ||||||
|  |             { | ||||||
|  |                 return RegExForwardSlashOnly; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             var route = reRoute.ReRouteIsCaseSensitive  |             var route = reRoute.ReRouteIsCaseSensitive  | ||||||
|                 ? $"{upstreamTemplate}{RegExMatchEndString}"  |                 ? $"{upstreamTemplate}{RegExMatchEndString}"  | ||||||
|                 : $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; |                 : $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; | ||||||
|   | |||||||
| @@ -1,14 +1,15 @@ | |||||||
| using Polly.Timeout; | using System; | ||||||
| using System; | using Polly.Timeout; | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Linq; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
|  |  | ||||||
| namespace Ocelot.Configuration | namespace Ocelot.Configuration | ||||||
| { | { | ||||||
|     public class QoSOptions |     public class QoSOptions | ||||||
|     { |     { | ||||||
|         public QoSOptions(int exceptionsAllowedBeforeBreaking, int durationofBreak, int timeoutValue, TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) |         public QoSOptions( | ||||||
|  |             int exceptionsAllowedBeforeBreaking,  | ||||||
|  |             int durationofBreak,  | ||||||
|  |             int timeoutValue,  | ||||||
|  |             TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) | ||||||
|         { |         { | ||||||
|             ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; |             ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; | ||||||
|             DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak); |             DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak); | ||||||
|   | |||||||
| @@ -25,12 +25,12 @@ namespace Ocelot.Configuration | |||||||
|             string loadBalancer,  |             string loadBalancer,  | ||||||
|             string downstreamHost,  |             string downstreamHost,  | ||||||
|             int downstreamPort,  |             int downstreamPort,  | ||||||
|             string loadBalancerKey,  |             string reRouteKey,  | ||||||
|             ServiceProviderConfiguraion serviceProviderConfiguraion, |             ServiceProviderConfiguraion serviceProviderConfiguraion, | ||||||
|             bool isQos, |             bool isQos, | ||||||
|             QoSOptions qos) |             QoSOptions qos) | ||||||
|         { |         { | ||||||
|             LoadBalancerKey = loadBalancerKey; |             ReRouteKey = reRouteKey; | ||||||
|             ServiceProviderConfiguraion = serviceProviderConfiguraion; |             ServiceProviderConfiguraion = serviceProviderConfiguraion; | ||||||
|             LoadBalancer = loadBalancer; |             LoadBalancer = loadBalancer; | ||||||
|             DownstreamHost = downstreamHost; |             DownstreamHost = downstreamHost; | ||||||
| @@ -57,7 +57,7 @@ namespace Ocelot.Configuration | |||||||
|             QosOptions = qos; |             QosOptions = qos; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public string LoadBalancerKey {get;private set;} |         public string ReRouteKey {get;private set;} | ||||||
|         public PathTemplate DownstreamPathTemplate { get; private set; } |         public PathTemplate DownstreamPathTemplate { get; private set; } | ||||||
|         public PathTemplate UpstreamPathTemplate { get; private set; } |         public PathTemplate UpstreamPathTemplate { get; private set; } | ||||||
|         public string UpstreamTemplatePattern { get; private set; } |         public string UpstreamTemplatePattern { get; private set; } | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ using Ocelot.Logging; | |||||||
| using Ocelot.QueryStrings; | using Ocelot.QueryStrings; | ||||||
| using Ocelot.Request.Builder; | using Ocelot.Request.Builder; | ||||||
| using Ocelot.Requester; | using Ocelot.Requester; | ||||||
|  | using Ocelot.Requester.QoS; | ||||||
| using Ocelot.Responder; | using Ocelot.Responder; | ||||||
| using Ocelot.ServiceDiscovery; | using Ocelot.ServiceDiscovery; | ||||||
|  |  | ||||||
| @@ -61,6 +62,8 @@ namespace Ocelot.DependencyInjection | |||||||
|         { |         { | ||||||
|             services.AddMvcCore().AddJsonFormatters(); |             services.AddMvcCore().AddJsonFormatters(); | ||||||
|             services.AddLogging(); |             services.AddLogging(); | ||||||
|  |             services.AddSingleton<IQosProviderHouse, QosProviderHouse>(); | ||||||
|  |             services.AddSingleton<IQoSProviderFactory, QoSProviderFactory>(); | ||||||
|             services.AddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>(); |             services.AddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>(); | ||||||
|             services.AddSingleton<ILoadBalancerFactory, LoadBalancerFactory>(); |             services.AddSingleton<ILoadBalancerFactory, LoadBalancerFactory>(); | ||||||
|             services.AddSingleton<ILoadBalancerHouse, LoadBalancerHouse>(); |             services.AddSingleton<ILoadBalancerHouse, LoadBalancerHouse>(); | ||||||
|   | |||||||
| @@ -30,6 +30,13 @@ namespace Ocelot.DownstreamRouteFinder.Finder | |||||||
|  |  | ||||||
|             foreach (var reRoute in applicableReRoutes) |             foreach (var reRoute in applicableReRoutes) | ||||||
|             { |             { | ||||||
|  |                 if (upstreamUrlPath == reRoute.UpstreamTemplatePattern) | ||||||
|  |                 { | ||||||
|  |                     var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamPathTemplate.Value); | ||||||
|  |  | ||||||
|  |                     return new OkResponse<DownstreamRoute>(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute)); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|                 var urlMatch = _urlMatcher.Match(upstreamUrlPath, reRoute.UpstreamTemplatePattern); |                 var urlMatch = _urlMatcher.Match(upstreamUrlPath, reRoute.UpstreamTemplatePattern); | ||||||
|  |  | ||||||
|                 if (urlMatch.Data.Match) |                 if (urlMatch.Data.Match) | ||||||
|   | |||||||
| @@ -25,6 +25,8 @@ | |||||||
|         ServicesAreNullError, |         ServicesAreNullError, | ||||||
|         ServicesAreEmptyError, |         ServicesAreEmptyError, | ||||||
|         UnableToFindServiceDiscoveryProviderError, |         UnableToFindServiceDiscoveryProviderError, | ||||||
|         UnableToFindLoadBalancerError |         UnableToFindLoadBalancerError, | ||||||
|  |         RequestTimedOutError, | ||||||
|  |         UnableToFindQoSProviderError | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ using Ocelot.LoadBalancer.LoadBalancers; | |||||||
| using Ocelot.Logging; | using Ocelot.Logging; | ||||||
| using Ocelot.Middleware; | using Ocelot.Middleware; | ||||||
| using Ocelot.QueryStrings.Middleware; | using Ocelot.QueryStrings.Middleware; | ||||||
| using Ocelot.ServiceDiscovery; |  | ||||||
|  |  | ||||||
| namespace Ocelot.LoadBalancer.Middleware | namespace Ocelot.LoadBalancer.Middleware | ||||||
| { | { | ||||||
| @@ -31,7 +30,7 @@ namespace Ocelot.LoadBalancer.Middleware | |||||||
|         { |         { | ||||||
|             _logger.LogDebug("started calling load balancing middleware"); |             _logger.LogDebug("started calling load balancing middleware"); | ||||||
|  |  | ||||||
|             var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.LoadBalancerKey); |             var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.ReRouteKey); | ||||||
|             if(loadBalancer.IsError) |             if(loadBalancer.IsError) | ||||||
|             { |             { | ||||||
|                 SetPipelineError(loadBalancer.Errors); |                 SetPipelineError(loadBalancer.Errors); | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ using System.Threading.Tasks; | |||||||
| using Microsoft.AspNetCore.Http; | using Microsoft.AspNetCore.Http; | ||||||
| using Ocelot.Responses; | using Ocelot.Responses; | ||||||
| using Ocelot.Configuration; | using Ocelot.Configuration; | ||||||
|  | using Ocelot.Requester.QoS; | ||||||
|  |  | ||||||
| namespace Ocelot.Request.Builder | namespace Ocelot.Request.Builder | ||||||
| { | { | ||||||
| @@ -18,7 +19,7 @@ namespace Ocelot.Request.Builder | |||||||
|             string contentType,  |             string contentType,  | ||||||
|             RequestId.RequestId requestId, |             RequestId.RequestId requestId, | ||||||
|             bool isQos, |             bool isQos, | ||||||
|             QoSOptions qos) |             IQoSProvider qosProvider) | ||||||
|         { |         { | ||||||
|             var request = await new RequestBuilder() |             var request = await new RequestBuilder() | ||||||
|                 .WithHttpMethod(httpMethod) |                 .WithHttpMethod(httpMethod) | ||||||
| @@ -30,7 +31,7 @@ namespace Ocelot.Request.Builder | |||||||
|                 .WithRequestId(requestId) |                 .WithRequestId(requestId) | ||||||
|                 .WithCookies(cookies) |                 .WithCookies(cookies) | ||||||
|                 .WithIsQos(isQos) |                 .WithIsQos(isQos) | ||||||
|                 .WithQos(qos) |                 .WithQos(qosProvider) | ||||||
|                 .Build(); |                 .Build(); | ||||||
|  |  | ||||||
|             return new OkResponse<Request>(request); |             return new OkResponse<Request>(request); | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| using System.IO; | using System.IO; | ||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
| using Microsoft.AspNetCore.Http; | using Microsoft.AspNetCore.Http; | ||||||
|  | using Ocelot.Requester.QoS; | ||||||
| using Ocelot.Responses; | using Ocelot.Responses; | ||||||
| using Ocelot.Configuration; |  | ||||||
|  |  | ||||||
| namespace Ocelot.Request.Builder | namespace Ocelot.Request.Builder | ||||||
| { | { | ||||||
| @@ -17,6 +17,6 @@ namespace Ocelot.Request.Builder | |||||||
|             string contentType, |             string contentType, | ||||||
|             RequestId.RequestId requestId, |             RequestId.RequestId requestId, | ||||||
|             bool isQos, |             bool isQos, | ||||||
|             QoSOptions qos); |             IQoSProvider qosProvider); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ using System.Net.Http.Headers; | |||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
| using Microsoft.AspNetCore.Http; | using Microsoft.AspNetCore.Http; | ||||||
| using Microsoft.Extensions.Primitives; | using Microsoft.Extensions.Primitives; | ||||||
| using Ocelot.Configuration; | using Ocelot.Requester.QoS; | ||||||
|  |  | ||||||
| namespace Ocelot.Request.Builder | namespace Ocelot.Request.Builder | ||||||
| { | { | ||||||
| @@ -24,7 +24,7 @@ namespace Ocelot.Request.Builder | |||||||
|         private IRequestCookieCollection _cookies; |         private IRequestCookieCollection _cookies; | ||||||
|         private readonly string[] _unsupportedHeaders = {"host"}; |         private readonly string[] _unsupportedHeaders = {"host"}; | ||||||
|         private bool _isQos; |         private bool _isQos; | ||||||
|         private QoSOptions _qos; |         private IQoSProvider _qoSProvider; | ||||||
|  |  | ||||||
|         public RequestBuilder WithHttpMethod(string httpMethod) |         public RequestBuilder WithHttpMethod(string httpMethod) | ||||||
|         { |         { | ||||||
| @@ -80,9 +80,9 @@ namespace Ocelot.Request.Builder | |||||||
|             return this; |             return this; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public RequestBuilder WithQos(QoSOptions qos) |         public RequestBuilder WithQos(IQoSProvider qoSProvider) | ||||||
|         { |         { | ||||||
|             _qos = qos; |             _qoSProvider = qoSProvider; | ||||||
|             return this; |             return this; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -105,7 +105,7 @@ namespace Ocelot.Request.Builder | |||||||
|  |  | ||||||
|             var cookieContainer = CreateCookieContainer(uri); |             var cookieContainer = CreateCookieContainer(uri); | ||||||
|  |  | ||||||
|             return new Request(httpRequestMessage, cookieContainer,_isQos,_qos); |             return new Request(httpRequestMessage, cookieContainer,_isQos, _qoSProvider); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private Uri CreateUri() |         private Uri CreateUri() | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
| using Microsoft.AspNetCore.Http; | using Microsoft.AspNetCore.Http; | ||||||
| using Microsoft.Extensions.Logging; |  | ||||||
| using Ocelot.Infrastructure.RequestData; | using Ocelot.Infrastructure.RequestData; | ||||||
| using Ocelot.Logging; | using Ocelot.Logging; | ||||||
| using Ocelot.Middleware; | using Ocelot.Middleware; | ||||||
| using Ocelot.Request.Builder; | using Ocelot.Request.Builder; | ||||||
|  | using Ocelot.Requester.QoS; | ||||||
|  |  | ||||||
| namespace Ocelot.Request.Middleware | namespace Ocelot.Request.Middleware | ||||||
| { | { | ||||||
| @@ -13,15 +13,18 @@ namespace Ocelot.Request.Middleware | |||||||
|         private readonly RequestDelegate _next; |         private readonly RequestDelegate _next; | ||||||
|         private readonly IRequestCreator _requestCreator; |         private readonly IRequestCreator _requestCreator; | ||||||
|         private readonly IOcelotLogger _logger; |         private readonly IOcelotLogger _logger; | ||||||
|  |         private readonly IQosProviderHouse _qosProviderHouse; | ||||||
|  |  | ||||||
|         public HttpRequestBuilderMiddleware(RequestDelegate next, |         public HttpRequestBuilderMiddleware(RequestDelegate next, | ||||||
|             IOcelotLoggerFactory loggerFactory, |             IOcelotLoggerFactory loggerFactory, | ||||||
|             IRequestScopedDataRepository requestScopedDataRepository,  |             IRequestScopedDataRepository requestScopedDataRepository,  | ||||||
|             IRequestCreator requestCreator) |             IRequestCreator requestCreator,  | ||||||
|  |             IQosProviderHouse qosProviderHouse) | ||||||
|             :base(requestScopedDataRepository) |             :base(requestScopedDataRepository) | ||||||
|         { |         { | ||||||
|             _next = next; |             _next = next; | ||||||
|             _requestCreator = requestCreator; |             _requestCreator = requestCreator; | ||||||
|  |             _qosProviderHouse = qosProviderHouse; | ||||||
|             _logger = loggerFactory.CreateLogger<HttpRequestBuilderMiddleware>(); |             _logger = loggerFactory.CreateLogger<HttpRequestBuilderMiddleware>(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -29,17 +32,35 @@ namespace Ocelot.Request.Middleware | |||||||
|         { |         { | ||||||
|             _logger.LogDebug("started calling request builder middleware"); |             _logger.LogDebug("started calling request builder middleware"); | ||||||
|  |  | ||||||
|  |             var qosProvider = _qosProviderHouse.Get(DownstreamRoute.ReRoute.ReRouteKey); | ||||||
|  |  | ||||||
|  |             if (qosProvider.IsError) | ||||||
|  |             { | ||||||
|  |                 _logger.LogDebug("IQosProviderHouse returned an error, setting pipeline error"); | ||||||
|  |  | ||||||
|  |                 SetPipelineError(qosProvider.Errors); | ||||||
|  |  | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             var buildResult = await _requestCreator |             var buildResult = await _requestCreator | ||||||
|                 .Build(context.Request.Method, DownstreamUrl, context.Request.Body, |                 .Build(context.Request.Method, | ||||||
|                     context.Request.Headers, context.Request.Cookies, context.Request.QueryString, |                     DownstreamUrl, | ||||||
|                     context.Request.ContentType, new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier), |                     context.Request.Body, | ||||||
|                     DownstreamRoute.ReRoute.IsQos,DownstreamRoute.ReRoute.QosOptions); |                     context.Request.Headers, | ||||||
|  |                     context.Request.Cookies, | ||||||
|  |                     context.Request.QueryString, | ||||||
|  |                     context.Request.ContentType, | ||||||
|  |                     new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier), | ||||||
|  |                     DownstreamRoute.ReRoute.IsQos, | ||||||
|  |                     qosProvider.Data); | ||||||
|  |  | ||||||
|             if (buildResult.IsError) |             if (buildResult.IsError) | ||||||
|             { |             { | ||||||
|                 _logger.LogDebug("IRequestCreator returned an error, setting pipeline error"); |                 _logger.LogDebug("IRequestCreator returned an error, setting pipeline error"); | ||||||
|  |  | ||||||
|                 SetPipelineError(buildResult.Errors); |                 SetPipelineError(buildResult.Errors); | ||||||
|  |  | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             _logger.LogDebug("setting upstream request"); |             _logger.LogDebug("setting upstream request"); | ||||||
|   | |||||||
| @@ -2,22 +2,27 @@ | |||||||
| using Ocelot.Values; | using Ocelot.Values; | ||||||
| using System.Net; | using System.Net; | ||||||
| using System.Net.Http; | using System.Net.Http; | ||||||
|  | using Ocelot.Requester.QoS; | ||||||
|  |  | ||||||
| namespace Ocelot.Request | namespace Ocelot.Request | ||||||
| { | { | ||||||
|     public class Request |     public class Request | ||||||
|     { |     { | ||||||
|         public Request(HttpRequestMessage httpRequestMessage, CookieContainer cookieContainer,bool isQos, QoSOptions qos) |         public Request( | ||||||
|  |             HttpRequestMessage httpRequestMessage,  | ||||||
|  |             CookieContainer cookieContainer, | ||||||
|  |             bool isQos, | ||||||
|  |             IQoSProvider qosProvider) | ||||||
|         { |         { | ||||||
|             HttpRequestMessage = httpRequestMessage; |             HttpRequestMessage = httpRequestMessage; | ||||||
|             CookieContainer = cookieContainer; |             CookieContainer = cookieContainer; | ||||||
|             IsQos = isQos; |             IsQos = isQos; | ||||||
|             Qos = qos; |             QosProvider = qosProvider; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public HttpRequestMessage HttpRequestMessage { get; private set; } |         public HttpRequestMessage HttpRequestMessage { get; private set; } | ||||||
|         public CookieContainer CookieContainer { get; private set; } |         public CookieContainer CookieContainer { get; private set; } | ||||||
|         public bool IsQos { get; private set; } |         public bool IsQos { get; private set; } | ||||||
|         public QoSOptions Qos { get; private set; } |         public IQoSProvider QosProvider { get; private set; } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,74 +0,0 @@ | |||||||
| using Ocelot.Logging; |  | ||||||
| using Polly; |  | ||||||
| using Polly.CircuitBreaker; |  | ||||||
| using Polly.Timeout; |  | ||||||
| using System; |  | ||||||
| using System.Net; |  | ||||||
| using System.Net.Http; |  | ||||||
| using System.Threading; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
|  |  | ||||||
| namespace Ocelot.Requester |  | ||||||
| { |  | ||||||
|     public class CircuitBreakingDelegatingHandler : DelegatingHandler |  | ||||||
|     { |  | ||||||
|         private readonly IOcelotLogger _logger; |  | ||||||
|         private readonly int _exceptionsAllowedBeforeBreaking; |  | ||||||
|         private readonly TimeSpan _durationOfBreak; |  | ||||||
|         private readonly Policy _circuitBreakerPolicy; |  | ||||||
|         private readonly TimeoutPolicy _timeoutPolicy; |  | ||||||
|  |  | ||||||
|         public CircuitBreakingDelegatingHandler(int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak,TimeSpan timeoutValue |  | ||||||
|             ,TimeoutStrategy timeoutStrategy, IOcelotLogger logger, HttpMessageHandler innerHandler) |  | ||||||
|             : base(innerHandler) |  | ||||||
|         { |  | ||||||
|             this._exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; |  | ||||||
|             this._durationOfBreak = durationOfBreak; |  | ||||||
|  |  | ||||||
|             _circuitBreakerPolicy = Policy |  | ||||||
|                 .Handle<HttpRequestException>() |  | ||||||
|                 .Or<TimeoutRejectedException>() |  | ||||||
|                 .Or<TimeoutException>() |  | ||||||
|                 .CircuitBreakerAsync( |  | ||||||
|                     exceptionsAllowedBeforeBreaking: exceptionsAllowedBeforeBreaking, |  | ||||||
|                     durationOfBreak: 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.") |  | ||||||
|                     ); |  | ||||||
|             _timeoutPolicy = Policy.TimeoutAsync(timeoutValue, timeoutStrategy); |  | ||||||
|             _logger = logger; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) |  | ||||||
|         { |  | ||||||
|             Task<HttpResponseMessage> responseTask = null; |  | ||||||
|  |  | ||||||
|             try |  | ||||||
|             { |  | ||||||
|                 responseTask = Policy.WrapAsync(_circuitBreakerPolicy, _timeoutPolicy).ExecuteAsync<HttpResponseMessage>(() => |  | ||||||
|                 { |  | ||||||
|                     return  base.SendAsync(request,cancellationToken); |  | ||||||
|                 }); |  | ||||||
|                 return responseTask; |  | ||||||
|             } |  | ||||||
|             catch (BrokenCircuitException ex) |  | ||||||
|             { |  | ||||||
|                 _logger.LogError($"Reached to allowed number of exceptions. Circuit is open. AllowedExceptionCount: {_exceptionsAllowedBeforeBreaking}, DurationOfBreak: {_durationOfBreak}",ex); |  | ||||||
|                 throw; |  | ||||||
|             } |  | ||||||
|             catch (HttpRequestException) |  | ||||||
|             { |  | ||||||
|                 return responseTask; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private static bool IsTransientFailure(HttpResponseMessage result) |  | ||||||
|         { |  | ||||||
|             return result.StatusCode >= HttpStatusCode.InternalServerError; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,35 +1,39 @@ | |||||||
| using Ocelot.Configuration; | using System; | ||||||
| using Ocelot.Logging; |  | ||||||
| using Ocelot.Values; |  | ||||||
| using Polly.Timeout; |  | ||||||
| using System; |  | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.Linq; | using System.Linq; | ||||||
| using System.Net.Http; | using System.Net.Http; | ||||||
| using System.Threading.Tasks; | using Ocelot.Logging; | ||||||
|  | using Ocelot.Requester.QoS; | ||||||
|  |  | ||||||
| namespace Ocelot.Requester | namespace Ocelot.Requester | ||||||
| { | { | ||||||
|     internal class HttpClientBuilder |     internal class HttpClientBuilder | ||||||
|     { |     { | ||||||
|         private readonly Dictionary<int, Func<DelegatingHandler>> handlers = new Dictionary<int, Func<DelegatingHandler>>(); |         private readonly Dictionary<int, Func<DelegatingHandler>> _handlers = new Dictionary<int, Func<DelegatingHandler>>(); | ||||||
|  |  | ||||||
|         public HttpClientBuilder WithCircuitBreaker(QoSOptions qos, IOcelotLogger logger, HttpMessageHandler innerHandler) |         public HttpClientBuilder WithQoS(IQoSProvider qoSProvider, IOcelotLogger logger, HttpMessageHandler innerHandler) | ||||||
|         { |         { | ||||||
|             handlers.Add(5000, () => new CircuitBreakingDelegatingHandler(qos.ExceptionsAllowedBeforeBreaking, qos.DurationOfBreak, qos.TimeoutValue, qos.TimeoutStrategy, logger, innerHandler)); |             _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qoSProvider, logger, innerHandler)); | ||||||
|             return this; |             return this; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         internal HttpClient Build(HttpMessageHandler innerHandler) |         internal HttpClient Build(HttpMessageHandler innerHandler) | ||||||
|         { |         { | ||||||
|             return handlers.Any() ? new HttpClient(CreateHttpMessageHandler()) : new HttpClient(innerHandler); |             return _handlers.Any() ?  | ||||||
|  |                 new HttpClient(CreateHttpMessageHandler()) :  | ||||||
|  |                 new HttpClient(innerHandler); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private HttpMessageHandler CreateHttpMessageHandler() |         private HttpMessageHandler CreateHttpMessageHandler() | ||||||
|         { |         { | ||||||
|             HttpMessageHandler httpMessageHandler = new HttpClientHandler(); |             HttpMessageHandler httpMessageHandler = new HttpClientHandler(); | ||||||
|  |  | ||||||
|             handlers.OrderByDescending(handler => handler.Key).Select(handler => handler.Value).Reverse().ToList().ForEach(handler => |             _handlers | ||||||
|  |                 .OrderByDescending(handler => handler.Key) | ||||||
|  |                 .Select(handler => handler.Value) | ||||||
|  |                 .Reverse() | ||||||
|  |                 .ToList() | ||||||
|  |                 .ForEach(handler => | ||||||
|             { |             { | ||||||
|                 var delegatingHandler = handler(); |                 var delegatingHandler = handler(); | ||||||
|                 delegatingHandler.InnerHandler = httpMessageHandler; |                 delegatingHandler.InnerHandler = httpMessageHandler; | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| using System; | using System; | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Net.Http; | using System.Net.Http; | ||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
| using Ocelot.Errors; |  | ||||||
| using Ocelot.Responses; |  | ||||||
| using Ocelot.Logging; | using Ocelot.Logging; | ||||||
|  | using Ocelot.Responses; | ||||||
|  | using Polly.CircuitBreaker; | ||||||
|  | using Polly.Timeout; | ||||||
|  |  | ||||||
| namespace Ocelot.Requester | namespace Ocelot.Requester | ||||||
| { | { | ||||||
| @@ -19,14 +19,15 @@ namespace Ocelot.Requester | |||||||
|  |  | ||||||
|         public async Task<Response<HttpResponseMessage>> GetResponse(Request.Request request) |         public async Task<Response<HttpResponseMessage>> GetResponse(Request.Request request) | ||||||
|         { |         { | ||||||
|             HttpClientBuilder builder = new HttpClientBuilder();     |             var builder = new HttpClientBuilder();     | ||||||
|  |  | ||||||
|             using (var handler = new HttpClientHandler { CookieContainer = request.CookieContainer }) |             using (var handler = new HttpClientHandler { CookieContainer = request.CookieContainer }) | ||||||
|             { |             { | ||||||
|                 if (request.IsQos) |                 if (request.IsQos) | ||||||
|                 { |                 { | ||||||
|                     builder.WithCircuitBreaker(request.Qos, _logger, handler); |                     builder.WithQoS(request.QosProvider, _logger, handler); | ||||||
|                 }            |                 }            | ||||||
|  |  | ||||||
|                 using (var httpClient = builder.Build(handler)) |                 using (var httpClient = builder.Build(handler)) | ||||||
|                 { |                 { | ||||||
|                     try |                     try | ||||||
| @@ -34,13 +35,19 @@ namespace Ocelot.Requester | |||||||
|                         var response = await httpClient.SendAsync(request.HttpRequestMessage); |                         var response = await httpClient.SendAsync(request.HttpRequestMessage); | ||||||
|                         return new OkResponse<HttpResponseMessage>(response); |                         return new OkResponse<HttpResponseMessage>(response); | ||||||
|                     } |                     } | ||||||
|                     catch (Exception exception) |                     catch (TimeoutRejectedException exception) | ||||||
|                     { |                     { | ||||||
|                         return |                         return | ||||||
|                             new ErrorResponse<HttpResponseMessage>(new List<Error> |                             new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception)); | ||||||
|                             { |                     } | ||||||
|                                 new UnableToCompleteRequestError(exception) |                     catch (BrokenCircuitException exception) | ||||||
|                             }); |                     { | ||||||
|  |                         return | ||||||
|  |                             new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception)); | ||||||
|  |                     } | ||||||
|  |                     catch (Exception exception) | ||||||
|  |                     { | ||||||
|  |                         return new ErrorResponse<HttpResponseMessage>(new UnableToCompleteRequestError(exception)); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -0,0 +1,47 @@ | |||||||
|  | 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, | ||||||
|  |             IOcelotLogger logger,  | ||||||
|  |             HttpMessageHandler innerHandler) | ||||||
|  |             : base(innerHandler) | ||||||
|  |         { | ||||||
|  |             _qoSProvider = qoSProvider; | ||||||
|  |             _logger = logger; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         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; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										145
									
								
								src/Ocelot/Requester/QoS/IQoSProviderFactory.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								src/Ocelot/Requester/QoS/IQoSProviderFactory.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,145 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Net.Http; | ||||||
|  | using Ocelot.Configuration; | ||||||
|  | using Ocelot.LoadBalancer.LoadBalancers; | ||||||
|  | using Ocelot.Logging; | ||||||
|  | using Ocelot.Responses; | ||||||
|  | using Polly; | ||||||
|  | using Polly.CircuitBreaker; | ||||||
|  | using Polly.Timeout; | ||||||
|  |  | ||||||
|  | namespace Ocelot.Requester.QoS | ||||||
|  | { | ||||||
|  |     public interface IQoSProviderFactory | ||||||
|  |     { | ||||||
|  |         IQoSProvider Get(ReRoute reRoute); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public class QoSProviderFactory : IQoSProviderFactory | ||||||
|  |     { | ||||||
|  |         private readonly IOcelotLoggerFactory _loggerFactory; | ||||||
|  |  | ||||||
|  |         public QoSProviderFactory(IOcelotLoggerFactory loggerFactory) | ||||||
|  |         { | ||||||
|  |             _loggerFactory = loggerFactory; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public IQoSProvider Get(ReRoute reRoute) | ||||||
|  |         { | ||||||
|  |             if (reRoute.IsQos) | ||||||
|  |             { | ||||||
|  |                 return new PollyQoSProvider(reRoute, _loggerFactory); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return new NoQoSProvider(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public interface IQoSProvider | ||||||
|  |     { | ||||||
|  |         CircuitBreaker CircuitBreaker { get; } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public class NoQoSProvider : IQoSProvider | ||||||
|  |     { | ||||||
|  |         public CircuitBreaker CircuitBreaker { get; } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public class PollyQoSProvider : IQoSProvider | ||||||
|  |     { | ||||||
|  |         private readonly CircuitBreakerPolicy _circuitBreakerPolicy; | ||||||
|  |         private readonly TimeoutPolicy _timeoutPolicy; | ||||||
|  |         private readonly IOcelotLogger _logger; | ||||||
|  |         private readonly CircuitBreaker _circuitBreaker; | ||||||
|  |  | ||||||
|  |         public PollyQoSProvider(ReRoute reRoute, IOcelotLoggerFactory loggerFactory) | ||||||
|  |         { | ||||||
|  |             _logger = loggerFactory.CreateLogger<PollyQoSProvider>(); | ||||||
|  |  | ||||||
|  |             _timeoutPolicy = Policy.TimeoutAsync(reRoute.QosOptions.TimeoutValue, reRoute.QosOptions.TimeoutStrategy); | ||||||
|  |  | ||||||
|  |             _circuitBreakerPolicy = Policy | ||||||
|  |                 .Handle<HttpRequestException>() | ||||||
|  |                 .Or<TimeoutRejectedException>() | ||||||
|  |                 .Or<TimeoutException>() | ||||||
|  |                 .CircuitBreakerAsync( | ||||||
|  |                     exceptionsAllowedBeforeBreaking: reRoute.QosOptions.ExceptionsAllowedBeforeBreaking, | ||||||
|  |                     durationOfBreak: 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; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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; } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     public interface IQosProviderHouse | ||||||
|  |     { | ||||||
|  |         Response<IQoSProvider> Get(string key); | ||||||
|  |         Response Add(string key, IQoSProvider loadBalancer); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public class QosProviderHouse : IQosProviderHouse | ||||||
|  |     { | ||||||
|  |         private readonly Dictionary<string, IQoSProvider> _qoSProviders; | ||||||
|  |  | ||||||
|  |         public QosProviderHouse() | ||||||
|  |         { | ||||||
|  |             _qoSProviders = new Dictionary<string, IQoSProvider>(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Response<IQoSProvider> Get(string key) | ||||||
|  |         { | ||||||
|  |             IQoSProvider qoSProvider; | ||||||
|  |  | ||||||
|  |             if (_qoSProviders.TryGetValue(key, out qoSProvider)) | ||||||
|  |             { | ||||||
|  |                 return new OkResponse<IQoSProvider>(_qoSProviders[key]); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return new ErrorResponse<IQoSProvider>(new List<Ocelot.Errors.Error>() | ||||||
|  |             { | ||||||
|  |                 new UnableToFindQoSProviderError($"unabe to find qos provider for {key}") | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Response Add(string key, IQoSProvider loadBalancer) | ||||||
|  |         { | ||||||
|  |             if (!_qoSProviders.ContainsKey(key)) | ||||||
|  |             { | ||||||
|  |                 _qoSProviders.Add(key, loadBalancer); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             _qoSProviders.Remove(key); | ||||||
|  |             _qoSProviders.Add(key, loadBalancer); | ||||||
|  |             return new OkResponse(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Ocelot.Errors; | ||||||
|  |  | ||||||
|  | namespace Ocelot.Requester.QoS | ||||||
|  | { | ||||||
|  |     public class UnableToFindQoSProviderError : Error | ||||||
|  |     { | ||||||
|  |         public UnableToFindQoSProviderError(string message)  | ||||||
|  |             : base(message, OcelotErrorCode.UnableToFindQoSProviderError) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								src/Ocelot/Requester/RequestTimedOutError.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Ocelot/Requester/RequestTimedOutError.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | using System; | ||||||
|  | using Ocelot.Errors; | ||||||
|  |  | ||||||
|  | namespace Ocelot.Requester | ||||||
|  | { | ||||||
|  |     public class RequestTimedOutError : Error | ||||||
|  |     { | ||||||
|  |         public RequestTimedOutError(Exception exception)  | ||||||
|  |             : base($"Timeout making http request, exception: {exception.Message}", OcelotErrorCode.RequestTimedOutError) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -22,6 +22,11 @@ namespace Ocelot.Responder | |||||||
|                 return new OkResponse<int>(403); |                 return new OkResponse<int>(403); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             if (errors.Any(e => e.Code == OcelotErrorCode.RequestTimedOutError)) | ||||||
|  |             { | ||||||
|  |                 return new OkResponse<int>(503); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             return new OkResponse<int>(404); |             return new OkResponse<int>(404); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -5,7 +5,13 @@ namespace Ocelot.Responses | |||||||
| { | { | ||||||
|     public class ErrorResponse<T> : Response<T> |     public class ErrorResponse<T> : Response<T> | ||||||
|     { |     { | ||||||
|         public ErrorResponse(List<Error> errors) : base(errors) |         public ErrorResponse(Error error)  | ||||||
|  |             : base(new List<Error> {error}) | ||||||
|  |         { | ||||||
|  |              | ||||||
|  |         } | ||||||
|  |         public ErrorResponse(List<Error> errors)  | ||||||
|  |             : base(errors) | ||||||
|         { |         { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										206
									
								
								test/Ocelot.AcceptanceTests/QoSTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								test/Ocelot.AcceptanceTests/QoSTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,206 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.IO; | ||||||
|  | using System.Net; | ||||||
|  | using System.Threading; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Builder; | ||||||
|  | using Microsoft.AspNetCore.Hosting; | ||||||
|  | using Microsoft.AspNetCore.Http; | ||||||
|  | using Ocelot.Configuration.File; | ||||||
|  | using TestStack.BDDfy; | ||||||
|  | using Xunit; | ||||||
|  |  | ||||||
|  | namespace Ocelot.AcceptanceTests | ||||||
|  | { | ||||||
|  |     public class QoSTests : IDisposable | ||||||
|  |     { | ||||||
|  |         private IWebHost _brokenService; | ||||||
|  |         private readonly Steps _steps; | ||||||
|  |         private int _requestCount; | ||||||
|  |         private IWebHost _workingService; | ||||||
|  |  | ||||||
|  |         public QoSTests() | ||||||
|  |         { | ||||||
|  |             _steps = new Steps(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void should_open_circuit_breaker_then_close() | ||||||
|  |         { | ||||||
|  |             var configuration = new FileConfiguration | ||||||
|  |             { | ||||||
|  |                 ReRoutes = new List<FileReRoute> | ||||||
|  |                 { | ||||||
|  |                     new FileReRoute | ||||||
|  |                     { | ||||||
|  |                         DownstreamPathTemplate = "/", | ||||||
|  |                         DownstreamScheme = "http", | ||||||
|  |                         DownstreamHost = "localhost", | ||||||
|  |                         DownstreamPort = 51879, | ||||||
|  |                         UpstreamPathTemplate = "/", | ||||||
|  |                         UpstreamHttpMethod = "Get", | ||||||
|  |                         QoSOptions = new FileQoSOptions | ||||||
|  |                         { | ||||||
|  |                             ExceptionsAllowedBeforeBreaking = 1, | ||||||
|  |                             TimeoutValue = 500, | ||||||
|  |                             DurationOfBreak = 1000 | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51879", "Hello from Laura")) | ||||||
|  |                 .Given(x => _steps.GivenThereIsAConfiguration(configuration)) | ||||||
|  |                 .Given(x => _steps.GivenOcelotIsRunning()) | ||||||
|  |                 .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) | ||||||
|  |                 .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) | ||||||
|  |                 .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) | ||||||
|  |                 .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) | ||||||
|  |                 .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) | ||||||
|  |                 .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) | ||||||
|  |                 .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) | ||||||
|  |                 .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) | ||||||
|  |                 .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) | ||||||
|  |                 .Given(x => x.GivenIWaitMilliseconds(3000)) | ||||||
|  |                 .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) | ||||||
|  |                 .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) | ||||||
|  |                 .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) | ||||||
|  |                 .BDDfy(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void open_circuit_should_not_effect_different_reRoute() | ||||||
|  |         { | ||||||
|  |             var configuration = new FileConfiguration | ||||||
|  |             { | ||||||
|  |                 ReRoutes = new List<FileReRoute> | ||||||
|  |                 { | ||||||
|  |                     new FileReRoute | ||||||
|  |                     { | ||||||
|  |                         DownstreamPathTemplate = "/", | ||||||
|  |                         DownstreamScheme = "http", | ||||||
|  |                         DownstreamHost = "localhost", | ||||||
|  |                         DownstreamPort = 51879, | ||||||
|  |                         UpstreamPathTemplate = "/", | ||||||
|  |                         UpstreamHttpMethod = "Get", | ||||||
|  |                         QoSOptions = new FileQoSOptions | ||||||
|  |                         { | ||||||
|  |                             ExceptionsAllowedBeforeBreaking = 1, | ||||||
|  |                             TimeoutValue = 500, | ||||||
|  |                             DurationOfBreak = 1000 | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     new FileReRoute | ||||||
|  |                     { | ||||||
|  |                         DownstreamPathTemplate = "/", | ||||||
|  |                         DownstreamScheme = "http", | ||||||
|  |                         DownstreamHost = "localhost", | ||||||
|  |                         DownstreamPort = 51880, | ||||||
|  |                         UpstreamPathTemplate = "working", | ||||||
|  |                         UpstreamHttpMethod = "Get", | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51879", "Hello from Laura")) | ||||||
|  |                 .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", 200, "Hello from Tom")) | ||||||
|  |                 .And(x => _steps.GivenThereIsAConfiguration(configuration)) | ||||||
|  |                 .And(x => _steps.GivenOcelotIsRunning()) | ||||||
|  |                 .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) | ||||||
|  |                 .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) | ||||||
|  |                 .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) | ||||||
|  |                 .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) | ||||||
|  |                 .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) | ||||||
|  |                 .And(x => _steps.WhenIGetUrlOnTheApiGateway("/working")) | ||||||
|  |                 .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) | ||||||
|  |                 .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) | ||||||
|  |                 .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) | ||||||
|  |                 .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) | ||||||
|  |                 .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) | ||||||
|  |                 .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) | ||||||
|  |                 .And(x => x.GivenIWaitMilliseconds(3000)) | ||||||
|  |                 .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) | ||||||
|  |                 .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) | ||||||
|  |                 .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) | ||||||
|  |                 .BDDfy(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void GivenIWaitMilliseconds(int ms) | ||||||
|  |         { | ||||||
|  |             Thread.Sleep(ms); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void GivenThereIsAPossiblyBrokenServiceRunningOn(string url, string responseBody) | ||||||
|  |         { | ||||||
|  |             _brokenService = new WebHostBuilder() | ||||||
|  |                 .UseUrls(url) | ||||||
|  |                 .UseKestrel() | ||||||
|  |                 .UseContentRoot(Directory.GetCurrentDirectory()) | ||||||
|  |                 .UseIISIntegration() | ||||||
|  |                 .UseUrls(url) | ||||||
|  |                 .Configure(app => | ||||||
|  |                 { | ||||||
|  |                     app.Run(async context => | ||||||
|  |                     { | ||||||
|  |                         //circuit starts closed | ||||||
|  |                         if (_requestCount == 0) | ||||||
|  |                         { | ||||||
|  |                             _requestCount++; | ||||||
|  |                             context.Response.StatusCode = 200; | ||||||
|  |                             await context.Response.WriteAsync(responseBody); | ||||||
|  |                             return; | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         //request one times out and polly throws exception, circuit opens | ||||||
|  |                         if (_requestCount == 1) | ||||||
|  |                         { | ||||||
|  |                             _requestCount++; | ||||||
|  |                             await Task.Delay(1000); | ||||||
|  |                             context.Response.StatusCode = 200; | ||||||
|  |                             return; | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         //after break closes we return 200 OK | ||||||
|  |                         if (_requestCount == 2) | ||||||
|  |                         { | ||||||
|  |                             context.Response.StatusCode = 200; | ||||||
|  |                             await context.Response.WriteAsync(responseBody); | ||||||
|  |                             return; | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |                 }) | ||||||
|  |                 .Build(); | ||||||
|  |  | ||||||
|  |             _brokenService.Start(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) | ||||||
|  |         { | ||||||
|  |             _workingService = new WebHostBuilder() | ||||||
|  |                 .UseUrls(url) | ||||||
|  |                 .UseKestrel() | ||||||
|  |                 .UseContentRoot(Directory.GetCurrentDirectory()) | ||||||
|  |                 .UseIISIntegration() | ||||||
|  |                 .UseUrls(url) | ||||||
|  |                 .Configure(app => | ||||||
|  |                 { | ||||||
|  |                     app.Run(async context => | ||||||
|  |                     { | ||||||
|  |                         context.Response.StatusCode = statusCode; | ||||||
|  |                         await context.Response.WriteAsync(responseBody); | ||||||
|  |                     }); | ||||||
|  |                 }) | ||||||
|  |                 .Build(); | ||||||
|  |  | ||||||
|  |             _workingService.Start(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void Dispose() | ||||||
|  |         { | ||||||
|  |             _workingService?.Dispose(); | ||||||
|  |             _brokenService?.Dispose(); | ||||||
|  |             _steps.Dispose(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -9,6 +9,7 @@ using Ocelot.Configuration.File; | |||||||
| using Ocelot.Configuration.Parser; | using Ocelot.Configuration.Parser; | ||||||
| using Ocelot.Configuration.Validator; | using Ocelot.Configuration.Validator; | ||||||
| using Ocelot.LoadBalancer.LoadBalancers; | using Ocelot.LoadBalancer.LoadBalancers; | ||||||
|  | using Ocelot.Requester.QoS; | ||||||
| using Ocelot.Responses; | using Ocelot.Responses; | ||||||
| using Shouldly; | using Shouldly; | ||||||
| using TestStack.BDDfy; | using TestStack.BDDfy; | ||||||
| @@ -28,9 +29,15 @@ namespace Ocelot.UnitTests.Configuration | |||||||
|         private readonly Mock<ILoadBalancerFactory> _loadBalancerFactory; |         private readonly Mock<ILoadBalancerFactory> _loadBalancerFactory; | ||||||
|         private readonly Mock<ILoadBalancerHouse> _loadBalancerHouse; |         private readonly Mock<ILoadBalancerHouse> _loadBalancerHouse; | ||||||
|         private readonly Mock<ILoadBalancer> _loadBalancer; |         private readonly Mock<ILoadBalancer> _loadBalancer; | ||||||
|  |         private readonly Mock<IQoSProviderFactory> _qosProviderFactory; | ||||||
|  |         private readonly Mock<IQosProviderHouse> _qosProviderHouse; | ||||||
|  |         private readonly Mock<IQoSProvider> _qosProvider; | ||||||
|  |  | ||||||
|         public FileConfigurationCreatorTests() |         public FileConfigurationCreatorTests() | ||||||
|         { |         { | ||||||
|  |             _qosProviderFactory = new Mock<IQoSProviderFactory>(); | ||||||
|  |             _qosProviderHouse = new Mock<IQosProviderHouse>(); | ||||||
|  |             _qosProvider = new Mock<IQoSProvider>(); | ||||||
|             _logger = new Mock<ILogger<FileOcelotConfigurationCreator>>(); |             _logger = new Mock<ILogger<FileOcelotConfigurationCreator>>(); | ||||||
|             _configParser = new Mock<IClaimToThingConfigurationParser>(); |             _configParser = new Mock<IClaimToThingConfigurationParser>(); | ||||||
|             _validator = new Mock<IConfigurationValidator>(); |             _validator = new Mock<IConfigurationValidator>(); | ||||||
| @@ -40,7 +47,8 @@ namespace Ocelot.UnitTests.Configuration | |||||||
|             _loadBalancer = new Mock<ILoadBalancer>(); |             _loadBalancer = new Mock<ILoadBalancer>(); | ||||||
|             _ocelotConfigurationCreator = new FileOcelotConfigurationCreator(  |             _ocelotConfigurationCreator = new FileOcelotConfigurationCreator(  | ||||||
|                 _fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object, |                 _fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object, | ||||||
|                 _loadBalancerFactory.Object, _loadBalancerHouse.Object); |                 _loadBalancerFactory.Object, _loadBalancerHouse.Object,  | ||||||
|  |                 _qosProviderFactory.Object, _qosProviderHouse.Object); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         [Fact] |         [Fact] | ||||||
| @@ -64,10 +72,39 @@ namespace Ocelot.UnitTests.Configuration | |||||||
|                                 .When(x => x.WhenICreateTheConfig()) |                                 .When(x => x.WhenICreateTheConfig()) | ||||||
|                                 .Then(x => x.TheLoadBalancerFactoryIsCalledCorrectly()) |                                 .Then(x => x.TheLoadBalancerFactoryIsCalledCorrectly()) | ||||||
|                                 .And(x => x.ThenTheLoadBalancerHouseIsCalledCorrectly()) |                                 .And(x => x.ThenTheLoadBalancerHouseIsCalledCorrectly()) | ||||||
|                                 |  | ||||||
|                     .BDDfy(); |                     .BDDfy(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void should_create_qos_provider() | ||||||
|  |         { | ||||||
|  |             this.Given(x => x.GivenTheConfigIs(new FileConfiguration | ||||||
|  |             { | ||||||
|  |                 ReRoutes = new List<FileReRoute> | ||||||
|  |                 { | ||||||
|  |                     new FileReRoute | ||||||
|  |                     { | ||||||
|  |                         DownstreamHost = "127.0.0.1", | ||||||
|  |                         UpstreamPathTemplate = "/api/products/{productId}", | ||||||
|  |                         DownstreamPathTemplate = "/products/{productId}", | ||||||
|  |                         UpstreamHttpMethod = "Get", | ||||||
|  |                         QoSOptions = new FileQoSOptions | ||||||
|  |                         { | ||||||
|  |                             TimeoutValue = 1, | ||||||
|  |                             DurationOfBreak = 1, | ||||||
|  |                             ExceptionsAllowedBeforeBreaking = 1 | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |             })) | ||||||
|  |                 .And(x => x.GivenTheConfigIsValid()) | ||||||
|  |                 .And(x => x.GivenTheQosProviderFactoryReturns()) | ||||||
|  |                 .When(x => x.WhenICreateTheConfig()) | ||||||
|  |                 .Then(x => x.TheQosProviderFactoryIsCalledCorrectly()) | ||||||
|  |                 .And(x => x.ThenTheQosProviderHouseIsCalledCorrectly()) | ||||||
|  |                 .BDDfy(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         [Fact] |         [Fact] | ||||||
|         public void should_use_downstream_host() |         public void should_use_downstream_host() | ||||||
|         { |         { | ||||||
| @@ -164,11 +201,11 @@ namespace Ocelot.UnitTests.Configuration | |||||||
|                                     .WithUpstreamPathTemplate("/api/products/{productId}") |                                     .WithUpstreamPathTemplate("/api/products/{productId}") | ||||||
|                                     .WithUpstreamHttpMethod("Get") |                                     .WithUpstreamHttpMethod("Get") | ||||||
|                                     .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") |                                     .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") | ||||||
|                                     .WithServiceName("ProductService") |  | ||||||
|                                     .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder() |                                     .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder() | ||||||
|                                         .WithUseServiceDiscovery(true) |                                         .WithUseServiceDiscovery(true) | ||||||
|                                         .WithServiceDiscoveryProvider("consul") |                                         .WithServiceDiscoveryProvider("consul") | ||||||
|                                         .WithServiceDiscoveryProviderHost("127.0.0.1") |                                         .WithServiceDiscoveryProviderHost("127.0.0.1") | ||||||
|  |                                         .WithServiceName("ProductService") | ||||||
|                                         .Build()) |                                         .Build()) | ||||||
|                                     .Build() |                                     .Build() | ||||||
|                             })) |                             })) | ||||||
| @@ -568,7 +605,7 @@ namespace Ocelot.UnitTests.Configuration | |||||||
|                         .WithDownstreamPathTemplate("/api/products/") |                         .WithDownstreamPathTemplate("/api/products/") | ||||||
|                         .WithUpstreamPathTemplate("/") |                         .WithUpstreamPathTemplate("/") | ||||||
|                         .WithUpstreamHttpMethod("Get") |                         .WithUpstreamHttpMethod("Get") | ||||||
|                         .WithUpstreamTemplatePattern("/$") |                         .WithUpstreamTemplatePattern("^/$") | ||||||
|                         .Build() |                         .Build() | ||||||
|                 })) |                 })) | ||||||
|                 .BDDfy(); |                 .BDDfy(); | ||||||
| @@ -643,5 +680,24 @@ namespace Ocelot.UnitTests.Configuration | |||||||
|             _loadBalancerHouse |             _loadBalancerHouse | ||||||
|                 .Verify(x => x.Add(It.IsAny<string>(), _loadBalancer.Object), Times.Once); |                 .Verify(x => x.Add(It.IsAny<string>(), _loadBalancer.Object), Times.Once); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         private void GivenTheQosProviderFactoryReturns() | ||||||
|  |         { | ||||||
|  |             _qosProviderFactory | ||||||
|  |                 .Setup(x => x.Get(It.IsAny<ReRoute>())) | ||||||
|  |                 .Returns(_qosProvider.Object); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void TheQosProviderFactoryIsCalledCorrectly() | ||||||
|  |         { | ||||||
|  |             _qosProviderFactory | ||||||
|  |                 .Verify(x => x.Get(It.IsAny<ReRoute>()), Times.Once); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void ThenTheQosProviderHouseIsCalledCorrectly() | ||||||
|  |         { | ||||||
|  |             _qosProviderHouse | ||||||
|  |                 .Verify(x => x.Add(It.IsAny<string>(), _qosProvider.Object), Times.Once); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -35,6 +35,37 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder | |||||||
|  |  | ||||||
|         [Fact] |         [Fact] | ||||||
|         public void should_return_route() |         public void should_return_route() | ||||||
|  |         { | ||||||
|  |             this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher")) | ||||||
|  |                 .And(x =>x.GivenTheTemplateVariableAndNameFinderReturns( | ||||||
|  |                         new OkResponse<List<UrlPathPlaceholderNameAndValue>>( | ||||||
|  |                             new List<UrlPathPlaceholderNameAndValue>()))) | ||||||
|  |                 .And(x => x.GivenTheConfigurationIs(new List<ReRoute> | ||||||
|  |                 { | ||||||
|  |                     new ReRouteBuilder() | ||||||
|  |                         .WithDownstreamPathTemplate("someDownstreamPath") | ||||||
|  |                         .WithUpstreamPathTemplate("someUpstreamPath") | ||||||
|  |                         .WithUpstreamHttpMethod("Get") | ||||||
|  |                         .WithUpstreamTemplatePattern("someUpstreamPath") | ||||||
|  |                         .Build() | ||||||
|  |                 })) | ||||||
|  |                 .And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true)))) | ||||||
|  |                 .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) | ||||||
|  |                 .When(x => x.WhenICallTheFinder()) | ||||||
|  |                 .Then( | ||||||
|  |                     x => x.ThenTheFollowingIsReturned(new DownstreamRoute( | ||||||
|  |                             new List<UrlPathPlaceholderNameAndValue>(), | ||||||
|  |                             new ReRouteBuilder() | ||||||
|  |                                 .WithDownstreamPathTemplate("someDownstreamPath") | ||||||
|  |                                 .WithUpstreamHttpMethod("Get") | ||||||
|  |                                 .Build() | ||||||
|  |                 ))) | ||||||
|  |                 .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) | ||||||
|  |                 .BDDfy(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void should_return_route_if_upstream_path_and_upstream_template_are_the_same() | ||||||
|         { |         { | ||||||
|             this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) |             this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) | ||||||
|                 .And( |                 .And( | ||||||
| @@ -61,7 +92,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder | |||||||
|                             .WithUpstreamHttpMethod("Get") |                             .WithUpstreamHttpMethod("Get") | ||||||
|                             .Build() |                             .Build() | ||||||
|                         ))) |                         ))) | ||||||
|                 .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) |                 .And(x => x.ThenTheUrlMatcherIsNotCalled()) | ||||||
|                 .BDDfy(); |                 .BDDfy(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -105,7 +136,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder | |||||||
|         [Fact] |         [Fact] | ||||||
|         public void should_not_return_route() |         public void should_not_return_route() | ||||||
|         { |         { | ||||||
|             this.Given(x => x.GivenThereIsAnUpstreamUrlPath("somePath")) |             this.Given(x => x.GivenThereIsAnUpstreamUrlPath("dontMatchPath")) | ||||||
|                  .And(x => x.GivenTheConfigurationIs(new List<ReRoute> |                  .And(x => x.GivenTheConfigurationIs(new List<ReRoute> | ||||||
|                      { |                      { | ||||||
|                         new ReRouteBuilder() |                         new ReRouteBuilder() | ||||||
| @@ -148,6 +179,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder | |||||||
|                 .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once); |                 .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         private void ThenTheUrlMatcherIsNotCalled() | ||||||
|  |         { | ||||||
|  |             _mockMatcher | ||||||
|  |                 .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Never); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         private void GivenTheUrlMatcherReturns(Response<UrlMatch> match) |         private void GivenTheUrlMatcherReturns(Response<UrlMatch> match) | ||||||
|         { |         { | ||||||
|             _match = match; |             _match = match; | ||||||
|   | |||||||
| @@ -18,6 +18,26 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher | |||||||
|             _urlMatcher = new RegExUrlMatcher(); |             _urlMatcher = new RegExUrlMatcher(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void should_not_match_forward_slash_only_regex() | ||||||
|  |         { | ||||||
|  |             this.Given(x => x.GivenIHaveAUpstreamPath("/working/")) | ||||||
|  |               .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) | ||||||
|  |               .When(x => x.WhenIMatchThePaths()) | ||||||
|  |               .And(x => x.ThenTheResultIsFalse()) | ||||||
|  |               .BDDfy(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void should_match_forward_slash_only_regex() | ||||||
|  |         { | ||||||
|  |             this.Given(x => x.GivenIHaveAUpstreamPath("/")) | ||||||
|  |               .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) | ||||||
|  |               .When(x => x.WhenIMatchThePaths()) | ||||||
|  |               .And(x => x.ThenTheResultIsTrue()) | ||||||
|  |               .BDDfy(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         [Fact] |         [Fact] | ||||||
|         public void should_find_match_when_template_smaller_than_valid_path() |         public void should_find_match_when_template_smaller_than_valid_path() | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ using Ocelot.Responses; | |||||||
| using TestStack.BDDfy; | using TestStack.BDDfy; | ||||||
| using Xunit; | using Xunit; | ||||||
| using Ocelot.Configuration; | using Ocelot.Configuration; | ||||||
|  | using Ocelot.Requester.QoS; | ||||||
|  |  | ||||||
| namespace Ocelot.UnitTests.Request | namespace Ocelot.UnitTests.Request | ||||||
| { | { | ||||||
| @@ -27,6 +28,7 @@ namespace Ocelot.UnitTests.Request | |||||||
|     { |     { | ||||||
|         private readonly Mock<IRequestCreator> _requestBuilder; |         private readonly Mock<IRequestCreator> _requestBuilder; | ||||||
|         private readonly Mock<IRequestScopedDataRepository> _scopedRepository; |         private readonly Mock<IRequestScopedDataRepository> _scopedRepository; | ||||||
|  |         private readonly Mock<IQosProviderHouse> _qosProviderHouse; | ||||||
|         private readonly string _url; |         private readonly string _url; | ||||||
|         private readonly TestServer _server; |         private readonly TestServer _server; | ||||||
|         private readonly HttpClient _client; |         private readonly HttpClient _client; | ||||||
| @@ -38,6 +40,7 @@ namespace Ocelot.UnitTests.Request | |||||||
|         public HttpRequestBuilderMiddlewareTests() |         public HttpRequestBuilderMiddlewareTests() | ||||||
|         { |         { | ||||||
|             _url = "http://localhost:51879"; |             _url = "http://localhost:51879"; | ||||||
|  |             _qosProviderHouse = new Mock<IQosProviderHouse>(); | ||||||
|             _requestBuilder = new Mock<IRequestCreator>(); |             _requestBuilder = new Mock<IRequestCreator>(); | ||||||
|             _scopedRepository = new Mock<IRequestScopedDataRepository>(); |             _scopedRepository = new Mock<IRequestScopedDataRepository>(); | ||||||
|             var builder = new WebHostBuilder() |             var builder = new WebHostBuilder() | ||||||
| @@ -45,6 +48,7 @@ namespace Ocelot.UnitTests.Request | |||||||
|               { |               { | ||||||
|                   x.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>(); |                   x.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>(); | ||||||
|                   x.AddLogging(); |                   x.AddLogging(); | ||||||
|  |                   x.AddSingleton(_qosProviderHouse.Object); | ||||||
|                   x.AddSingleton(_requestBuilder.Object); |                   x.AddSingleton(_requestBuilder.Object); | ||||||
|                   x.AddSingleton(_scopedRepository.Object); |                   x.AddSingleton(_scopedRepository.Object); | ||||||
|               }) |               }) | ||||||
| @@ -72,15 +76,22 @@ namespace Ocelot.UnitTests.Request | |||||||
|                     .WithUpstreamHttpMethod("Get") |                     .WithUpstreamHttpMethod("Get") | ||||||
|                     .Build()); |                     .Build()); | ||||||
|  |  | ||||||
|  |  | ||||||
|             this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) |             this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) | ||||||
|  |                 .And(x => x.GivenTheQosProviderHouseReturns(new OkResponse<IQoSProvider>(new NoQoSProvider()))) | ||||||
|                 .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) |                 .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) | ||||||
|                 .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), new CookieContainer(), true, new QoSOptions(3, 8 ,5000, Polly.Timeout.TimeoutStrategy.Pessimistic)))) |                 .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), new CookieContainer(), true, new NoQoSProvider()))) | ||||||
|                 .When(x => x.WhenICallTheMiddleware()) |                 .When(x => x.WhenICallTheMiddleware()) | ||||||
|                 .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) |                 .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) | ||||||
|                 .BDDfy(); |                 .BDDfy(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         private void GivenTheQosProviderHouseReturns(Response<IQoSProvider> qosProvider) | ||||||
|  |         { | ||||||
|  |             _qosProviderHouse | ||||||
|  |                 .Setup(x => x.Get(It.IsAny<string>())) | ||||||
|  |                 .Returns(qosProvider); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) |         private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) | ||||||
|         { |         { | ||||||
|             _downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute); |             _downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute); | ||||||
| @@ -94,7 +105,7 @@ namespace Ocelot.UnitTests.Request | |||||||
|             _request = new OkResponse<Ocelot.Request.Request>(request); |             _request = new OkResponse<Ocelot.Request.Request>(request); | ||||||
|             _requestBuilder |             _requestBuilder | ||||||
|                 .Setup(x => x.Build(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<IHeaderDictionary>(), |                 .Setup(x => x.Build(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<IHeaderDictionary>(), | ||||||
|                 It.IsAny<IRequestCookieCollection>(), It.IsAny<QueryString>(), It.IsAny<string>(), It.IsAny<Ocelot.RequestId.RequestId>(),It.IsAny<bool>(), It.IsAny<QoSOptions>())) |                 It.IsAny<IRequestCookieCollection>(), It.IsAny<QueryString>(), It.IsAny<string>(), It.IsAny<Ocelot.RequestId.RequestId>(),It.IsAny<bool>(), It.IsAny<IQoSProvider>())) | ||||||
|                 .ReturnsAsync(_request); |                 .ReturnsAsync(_request); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ using Shouldly; | |||||||
| using TestStack.BDDfy; | using TestStack.BDDfy; | ||||||
| using Xunit; | using Xunit; | ||||||
| using Ocelot.Configuration; | using Ocelot.Configuration; | ||||||
|  | using Ocelot.Requester.QoS; | ||||||
|  |  | ||||||
| namespace Ocelot.UnitTests.Request | namespace Ocelot.UnitTests.Request | ||||||
| { | { | ||||||
| @@ -27,7 +28,7 @@ namespace Ocelot.UnitTests.Request | |||||||
|         private Response<Ocelot.Request.Request> _result; |         private Response<Ocelot.Request.Request> _result; | ||||||
|         private Ocelot.RequestId.RequestId _requestId; |         private Ocelot.RequestId.RequestId _requestId; | ||||||
|         private bool _isQos; |         private bool _isQos; | ||||||
|         private QoSOptions _qos; |         private IQoSProvider _qoSProvider; | ||||||
|  |  | ||||||
|         public RequestBuilderTests() |         public RequestBuilderTests() | ||||||
|         { |         { | ||||||
| @@ -40,7 +41,7 @@ namespace Ocelot.UnitTests.Request | |||||||
|         { |         { | ||||||
|             this.Given(x => x.GivenIHaveHttpMethod("GET")) |             this.Given(x => x.GivenIHaveHttpMethod("GET")) | ||||||
|                 .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) |                 .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) | ||||||
|                 .And(x=> x.GivenTheQos(true,new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) |                 .And(x=> x.GivenTheQos(true, new NoQoSProvider())) | ||||||
|                 .When(x => x.WhenICreateARequest()) |                 .When(x => x.WhenICreateARequest()) | ||||||
|                 .And(x => x.ThenTheCorrectDownstreamUrlIsUsed("http://www.bbc.co.uk/")) |                 .And(x => x.ThenTheCorrectDownstreamUrlIsUsed("http://www.bbc.co.uk/")) | ||||||
|                 .BDDfy(); |                 .BDDfy(); | ||||||
| @@ -51,7 +52,7 @@ namespace Ocelot.UnitTests.Request | |||||||
|         { |         { | ||||||
|             this.Given(x => x.GivenIHaveHttpMethod("POST")) |             this.Given(x => x.GivenIHaveHttpMethod("POST")) | ||||||
|                 .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) |                 .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) | ||||||
|                 .And(x => x.GivenTheQos(true,new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) |                 .And(x => x.GivenTheQos(true, new NoQoSProvider())) | ||||||
|  |  | ||||||
|                 .When(x => x.WhenICreateARequest()) |                 .When(x => x.WhenICreateARequest()) | ||||||
|                 .And(x => x.ThenTheCorrectHttpMethodIsUsed(HttpMethod.Post)) |                 .And(x => x.ThenTheCorrectHttpMethodIsUsed(HttpMethod.Post)) | ||||||
| @@ -65,7 +66,7 @@ namespace Ocelot.UnitTests.Request | |||||||
|                 .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) |                 .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) | ||||||
|                 .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) |                 .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) | ||||||
|                 .And(x => x.GivenTheContentTypeIs("application/json")) |                 .And(x => x.GivenTheContentTypeIs("application/json")) | ||||||
|                               .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) |                               .And(x => x.GivenTheQos(true, new NoQoSProvider())) | ||||||
|  |  | ||||||
|                               .When(x => x.WhenICreateARequest()) |                               .When(x => x.WhenICreateARequest()) | ||||||
|                .And(x => x.ThenTheCorrectContentIsUsed(new StringContent("Hi from Tom"))) |                .And(x => x.ThenTheCorrectContentIsUsed(new StringContent("Hi from Tom"))) | ||||||
| @@ -79,7 +80,7 @@ namespace Ocelot.UnitTests.Request | |||||||
|                 .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) |                 .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) | ||||||
|                 .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) |                 .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) | ||||||
|                 .And(x => x.GivenTheContentTypeIs("application/json")) |                 .And(x => x.GivenTheContentTypeIs("application/json")) | ||||||
|                 .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) |                 .And(x => x.GivenTheQos(true, new NoQoSProvider())) | ||||||
|  |  | ||||||
|                .When(x => x.WhenICreateARequest()) |                .When(x => x.WhenICreateARequest()) | ||||||
|                .And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary |                .And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary | ||||||
| @@ -98,7 +99,7 @@ namespace Ocelot.UnitTests.Request | |||||||
|                 .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) |                 .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) | ||||||
|                 .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) |                 .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) | ||||||
|                 .And(x => x.GivenTheContentTypeIs("application/json; charset=utf-8")) |                 .And(x => x.GivenTheContentTypeIs("application/json; charset=utf-8")) | ||||||
|                                 .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) |                                 .And(x => x.GivenTheQos(true, new NoQoSProvider())) | ||||||
|  |  | ||||||
|                .When(x => x.WhenICreateARequest()) |                .When(x => x.WhenICreateARequest()) | ||||||
|                .And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary |                .And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary | ||||||
| @@ -119,7 +120,7 @@ namespace Ocelot.UnitTests.Request | |||||||
|                 { |                 { | ||||||
|                     {"ChopSticks", "Bubbles" } |                     {"ChopSticks", "Bubbles" } | ||||||
|                 })) |                 })) | ||||||
|                                 .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) |                                 .And(x => x.GivenTheQos(true, new NoQoSProvider())) | ||||||
|  |  | ||||||
|                 .When(x => x.WhenICreateARequest()) |                 .When(x => x.WhenICreateARequest()) | ||||||
|                 .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary |                 .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary | ||||||
| @@ -138,7 +139,7 @@ namespace Ocelot.UnitTests.Request | |||||||
|                 .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) |                 .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) | ||||||
|                 .And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary())) |                 .And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary())) | ||||||
|                 .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", requestId))) |                 .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", requestId))) | ||||||
|                               .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) |                               .And(x => x.GivenTheQos(true, new NoQoSProvider())) | ||||||
|   .When(x => x.WhenICreateARequest()) |   .When(x => x.WhenICreateARequest()) | ||||||
|                 .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary |                 .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary | ||||||
|                 { |                 { | ||||||
| @@ -157,7 +158,7 @@ namespace Ocelot.UnitTests.Request | |||||||
|                     {"RequestId", "534534gv54gv45g" } |                     {"RequestId", "534534gv54gv45g" } | ||||||
|                 })) |                 })) | ||||||
|                 .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", Guid.NewGuid().ToString()))) |                 .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", Guid.NewGuid().ToString()))) | ||||||
|                                .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) |                                .And(x => x.GivenTheQos(true, new NoQoSProvider())) | ||||||
|  .When(x => x.WhenICreateARequest()) |  .When(x => x.WhenICreateARequest()) | ||||||
|                 .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary |                 .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary | ||||||
|                 { |                 { | ||||||
| @@ -177,7 +178,7 @@ namespace Ocelot.UnitTests.Request | |||||||
|                 .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) |                 .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) | ||||||
|                 .And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary())) |                 .And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary())) | ||||||
|                 .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId(requestIdKey, requestIdValue))) |                 .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId(requestIdKey, requestIdValue))) | ||||||
|                               .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) |                               .And(x => x.GivenTheQos(true, new NoQoSProvider())) | ||||||
|   .When(x => x.WhenICreateARequest()) |   .When(x => x.WhenICreateARequest()) | ||||||
|                 .And(x => x.ThenTheRequestIdIsNotInTheHeaders()) |                 .And(x => x.ThenTheRequestIdIsNotInTheHeaders()) | ||||||
|                 .BDDfy(); |                 .BDDfy(); | ||||||
| @@ -188,10 +189,10 @@ namespace Ocelot.UnitTests.Request | |||||||
|             _requestId = requestId; |             _requestId = requestId; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private void GivenTheQos(bool isQos, QoSOptions qos) |         private void GivenTheQos(bool isQos, IQoSProvider qoSProvider) | ||||||
|         { |         { | ||||||
|             _isQos = isQos; |             _isQos = isQos; | ||||||
|             _qos = qos; |             _qoSProvider = qoSProvider; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         [Fact] |         [Fact] | ||||||
| @@ -304,7 +305,7 @@ namespace Ocelot.UnitTests.Request | |||||||
|         private void WhenICreateARequest() |         private void WhenICreateARequest() | ||||||
|         { |         { | ||||||
|             _result = _requestCreator.Build(_httpMethod, _downstreamUrl, _content?.ReadAsStreamAsync().Result, _headers, |             _result = _requestCreator.Build(_httpMethod, _downstreamUrl, _content?.ReadAsStreamAsync().Result, _headers, | ||||||
|                 _cookies, _query, _contentType, _requestId,_isQos,_qos).Result; |                 _cookies, _query, _contentType, _requestId,_isQos,_qoSProvider).Result; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ using Ocelot.Logging; | |||||||
| using Ocelot.QueryStrings.Middleware; | using Ocelot.QueryStrings.Middleware; | ||||||
| using Ocelot.Requester; | using Ocelot.Requester; | ||||||
| using Ocelot.Requester.Middleware; | using Ocelot.Requester.Middleware; | ||||||
|  | using Ocelot.Requester.QoS; | ||||||
| using Ocelot.Responder; | using Ocelot.Responder; | ||||||
| using Ocelot.Responses; | using Ocelot.Responses; | ||||||
| using TestStack.BDDfy; | using TestStack.BDDfy; | ||||||
| @@ -61,7 +62,7 @@ namespace Ocelot.UnitTests.Requester | |||||||
|         [Fact] |         [Fact] | ||||||
|         public void should_call_scoped_data_repository_correctly() |         public void should_call_scoped_data_repository_correctly() | ||||||
|         { |         { | ||||||
|             this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),new CookieContainer(),true, new Ocelot.Configuration.QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic)))) |             this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),new CookieContainer(),true, new NoQoSProvider()))) | ||||||
|                 .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) |                 .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) | ||||||
|                 .And(x => x.GivenTheScopedRepoReturns()) |                 .And(x => x.GivenTheScopedRepoReturns()) | ||||||
|                 .When(x => x.WhenICallTheMiddleware()) |                 .When(x => x.WhenICallTheMiddleware()) | ||||||
|   | |||||||
							
								
								
									
										80
									
								
								test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | |||||||
|  | using Moq; | ||||||
|  | using Ocelot.Configuration; | ||||||
|  | using Ocelot.Configuration.Builder; | ||||||
|  | using Ocelot.Logging; | ||||||
|  | using Ocelot.Requester.QoS; | ||||||
|  | using Shouldly; | ||||||
|  | using TestStack.BDDfy; | ||||||
|  | using Xunit; | ||||||
|  |  | ||||||
|  | namespace Ocelot.UnitTests.Requester | ||||||
|  | { | ||||||
|  |     public class QoSProviderFactoryTests | ||||||
|  |     { | ||||||
|  |         private readonly IQoSProviderFactory _factory; | ||||||
|  |         private ReRoute _reRoute; | ||||||
|  |         private IQoSProvider _result; | ||||||
|  |         private Mock<IOcelotLoggerFactory> _loggerFactory; | ||||||
|  |         private Mock<IOcelotLogger> _logger; | ||||||
|  |  | ||||||
|  |         public QoSProviderFactoryTests() | ||||||
|  |         { | ||||||
|  |             _logger = new Mock<IOcelotLogger>(); | ||||||
|  |             _loggerFactory = new Mock<IOcelotLoggerFactory>(); | ||||||
|  |             _loggerFactory | ||||||
|  |                 .Setup(x => x.CreateLogger<PollyQoSProvider>()) | ||||||
|  |                 .Returns(_logger.Object); | ||||||
|  |             _factory = new QoSProviderFactory(_loggerFactory.Object); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void should_return_no_qos_provider() | ||||||
|  |         { | ||||||
|  |             var reRoute = new ReRouteBuilder() | ||||||
|  |                 .WithUpstreamHttpMethod("get") | ||||||
|  |                 .WithIsQos(false) | ||||||
|  |                 .Build(); | ||||||
|  |  | ||||||
|  |             this.Given(x => x.GivenAReRoute(reRoute)) | ||||||
|  |                 .When(x => x.WhenIGetTheQoSProvider()) | ||||||
|  |                 .Then(x => x.ThenTheQoSProviderIsReturned<NoQoSProvider>()) | ||||||
|  |                 .BDDfy(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void should_return_polly_qos_provider() | ||||||
|  |         { | ||||||
|  |             var qosOptions = new QoSOptionsBuilder() | ||||||
|  |                 .WithTimeoutValue(100) | ||||||
|  |                 .WithDurationOfBreak(100) | ||||||
|  |                 .WithExceptionsAllowedBeforeBreaking(100) | ||||||
|  |                 .Build(); | ||||||
|  |  | ||||||
|  |             var reRoute = new ReRouteBuilder() | ||||||
|  |                .WithUpstreamHttpMethod("get") | ||||||
|  |                .WithIsQos(true) | ||||||
|  |                .WithQosOptions(qosOptions) | ||||||
|  |                .Build(); | ||||||
|  |  | ||||||
|  |             this.Given(x => x.GivenAReRoute(reRoute)) | ||||||
|  |                 .When(x => x.WhenIGetTheQoSProvider()) | ||||||
|  |                 .Then(x => x.ThenTheQoSProviderIsReturned<PollyQoSProvider>()) | ||||||
|  |                 .BDDfy(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void GivenAReRoute(ReRoute reRoute) | ||||||
|  |         { | ||||||
|  |             _reRoute = reRoute; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void WhenIGetTheQoSProvider() | ||||||
|  |         { | ||||||
|  |             _result = _factory.Get(_reRoute); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void ThenTheQoSProviderIsReturned<T>() | ||||||
|  |         { | ||||||
|  |             _result.ShouldBeOfType<T>(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										117
									
								
								test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | |||||||
|  | using Ocelot.Requester.QoS; | ||||||
|  | using Ocelot.Responses; | ||||||
|  | using Shouldly; | ||||||
|  | using TestStack.BDDfy; | ||||||
|  | using Xunit; | ||||||
|  |  | ||||||
|  | namespace Ocelot.UnitTests.Requester | ||||||
|  | { | ||||||
|  |     public class QosProviderHouseTests | ||||||
|  |     { | ||||||
|  |         private IQoSProvider _qoSProvider; | ||||||
|  |         private readonly QosProviderHouse _qosProviderHouse; | ||||||
|  |         private Response _addResult; | ||||||
|  |         private Response<IQoSProvider> _getResult; | ||||||
|  |         private string _key; | ||||||
|  |  | ||||||
|  |         public QosProviderHouseTests() | ||||||
|  |         { | ||||||
|  |             _qosProviderHouse = new QosProviderHouse(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void should_store_qos_provider() | ||||||
|  |         { | ||||||
|  |             var key = "test"; | ||||||
|  |  | ||||||
|  |             this.Given(x => x.GivenThereIsAQoSProvider(key, new FakeQoSProvider())) | ||||||
|  |                 .When(x => x.WhenIAddTheQoSProvider()) | ||||||
|  |                 .Then(x => x.ThenItIsAdded()) | ||||||
|  |                 .BDDfy(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void should_get_qos_provider() | ||||||
|  |         { | ||||||
|  |             var key = "test"; | ||||||
|  |  | ||||||
|  |             this.Given(x => x.GivenThereIsAQoSProvider(key, new FakeQoSProvider())) | ||||||
|  |                 .When(x => x.WhenWeGetTheQoSProvider(key)) | ||||||
|  |                 .Then(x => x.ThenItIsReturned()) | ||||||
|  |                 .BDDfy(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void should_store_qos_providers_by_key() | ||||||
|  |         { | ||||||
|  |             var key = "test"; | ||||||
|  |             var keyTwo = "testTwo"; | ||||||
|  |  | ||||||
|  |             this.Given(x => x.GivenThereIsAQoSProvider(key, new FakeQoSProvider())) | ||||||
|  |                 .And(x => x.GivenThereIsAQoSProvider(keyTwo, new FakePollyQoSProvider())) | ||||||
|  |                 .When(x => x.WhenWeGetTheQoSProvider(key)) | ||||||
|  |                 .Then(x => x.ThenTheQoSProviderIs<FakeQoSProvider>()) | ||||||
|  |                 .When(x => x.WhenWeGetTheQoSProvider(keyTwo)) | ||||||
|  |                 .Then(x => x.ThenTheQoSProviderIs<FakePollyQoSProvider>()) | ||||||
|  |                 .BDDfy(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void should_return_error_if_no_qos_provider_with_key() | ||||||
|  |         { | ||||||
|  |             this.When(x => x.WhenWeGetTheQoSProvider("test")) | ||||||
|  |             .Then(x => x.ThenAnErrorIsReturned()) | ||||||
|  |             .BDDfy(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void ThenAnErrorIsReturned() | ||||||
|  |         { | ||||||
|  |             _getResult.IsError.ShouldBeTrue(); | ||||||
|  |             _getResult.Errors[0].ShouldBeOfType<UnableToFindQoSProviderError>(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void ThenTheQoSProviderIs<T>() | ||||||
|  |         { | ||||||
|  |             _getResult.Data.ShouldBeOfType<T>(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void ThenItIsAdded() | ||||||
|  |         { | ||||||
|  |             _addResult.IsError.ShouldBe(false); | ||||||
|  |             _addResult.ShouldBeOfType<OkResponse>(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void WhenIAddTheQoSProvider() | ||||||
|  |         { | ||||||
|  |             _addResult = _qosProviderHouse.Add(_key, _qoSProvider); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         private void GivenThereIsAQoSProvider(string key, IQoSProvider qoSProvider) | ||||||
|  |         { | ||||||
|  |             _key = key; | ||||||
|  |             _qoSProvider = qoSProvider; | ||||||
|  |             WhenIAddTheQoSProvider(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void WhenWeGetTheQoSProvider(string key) | ||||||
|  |         { | ||||||
|  |             _getResult = _qosProviderHouse.Get(key); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void ThenItIsReturned() | ||||||
|  |         { | ||||||
|  |             _getResult.Data.ShouldBe(_qoSProvider); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         class FakeQoSProvider : IQoSProvider | ||||||
|  |         { | ||||||
|  |             public CircuitBreaker CircuitBreaker { get; } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         class FakePollyQoSProvider : IQoSProvider | ||||||
|  |         { | ||||||
|  |             public CircuitBreaker CircuitBreaker { get; } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,6 +1,8 @@ | |||||||
| using System.Collections.Generic; | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
| using Ocelot.Errors; | using Ocelot.Errors; | ||||||
| using Ocelot.Middleware; | using Ocelot.Middleware; | ||||||
|  | using Ocelot.Requester; | ||||||
| using Ocelot.Responder; | using Ocelot.Responder; | ||||||
| using Ocelot.Responses; | using Ocelot.Responses; | ||||||
| using Shouldly; | using Shouldly; | ||||||
| @@ -20,6 +22,18 @@ namespace Ocelot.UnitTests.Responder | |||||||
|             _codeMapper = new ErrorsToHttpStatusCodeMapper(); |             _codeMapper = new ErrorsToHttpStatusCodeMapper(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void should_return_timeout() | ||||||
|  |         { | ||||||
|  |             this.Given(x => x.GivenThereAreErrors(new List<Error> | ||||||
|  |                 { | ||||||
|  |                     new RequestTimedOutError(new Exception()) | ||||||
|  |                 })) | ||||||
|  |                .When(x => x.WhenIGetErrorStatusCode()) | ||||||
|  |                .Then(x => x.ThenTheResponseIsStatusCodeIs(503)) | ||||||
|  |                .BDDfy(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         [Fact] |         [Fact] | ||||||
|         public void should_create_unauthenticated_response_code() |         public void should_create_unauthenticated_response_code() | ||||||
|         { |         { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Tom Pallister
					Tom Pallister