mirror of
				https://github.com/nsnail/Ocelot.git
				synced 2025-10-31 23:15:28 +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 | ||||
| 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.  | ||||
| This is encapsulated in  IOcelotLogger / IOcelotLoggerFactory with an implementation  | ||||
| for the standard asp.net core logging stuff at the moment.  | ||||
| Ocelot supports one QoS capability at the current time. You can set on a per ReRoute basis if you  | ||||
| want to use a circuit breaker when making requests to a downstream service. This uses the an awesome | ||||
| .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  | ||||
| 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. | ||||
| Add the following section to a ReRoute configuration.  | ||||
|  | ||||
| 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. | ||||
| 		"QoSOptions": { | ||||
| 			"ExceptionsAllowedBeforeBreaking":3, | ||||
| 			"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 | ||||
|  | ||||
| @@ -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 | ||||
| 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 | ||||
|  | ||||
| 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 bool _isCached; | ||||
|         private CacheOptions _fileCacheOptions; | ||||
|         private string _serviceName; | ||||
|         private string _downstreamScheme; | ||||
|         private string _downstreamHost; | ||||
|         private int _downstreamPort; | ||||
| @@ -49,12 +48,6 @@ namespace Ocelot.Configuration.Builder | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         public ReRouteBuilder WithServiceName(string serviceName) | ||||
|         { | ||||
|             _serviceName = serviceName; | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         public ReRouteBuilder WithDownstreamPathTemplate(string input) | ||||
|         { | ||||
|             _downstreamPathTemplate = input; | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Net.Http; | ||||
| using System.Text; | ||||
| using System.Threading.Tasks; | ||||
| using Microsoft.Extensions.Logging; | ||||
| @@ -10,9 +9,9 @@ using Ocelot.Configuration.File; | ||||
| using Ocelot.Configuration.Parser; | ||||
| using Ocelot.Configuration.Validator; | ||||
| using Ocelot.LoadBalancer.LoadBalancers; | ||||
| using Ocelot.Requester.QoS; | ||||
| using Ocelot.Responses; | ||||
| using Ocelot.Utilities; | ||||
| using Ocelot.Values; | ||||
|  | ||||
| namespace Ocelot.Configuration.Creator | ||||
| { | ||||
| @@ -26,11 +25,14 @@ namespace Ocelot.Configuration.Creator | ||||
|         private const string RegExMatchEverything = ".*"; | ||||
|         private const string RegExMatchEndString = "$"; | ||||
|         private const string RegExIgnoreCase = "(?i)"; | ||||
|         private const string RegExForwardSlashOnly = "^/$"; | ||||
|  | ||||
|         private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser; | ||||
|         private readonly ILogger<FileOcelotConfigurationCreator> _logger; | ||||
|         private readonly ILoadBalancerFactory _loadBalanceFactory; | ||||
|         private readonly ILoadBalancerHouse _loadBalancerHouse; | ||||
|         private readonly IQoSProviderFactory _qoSProviderFactory; | ||||
|         private readonly IQosProviderHouse _qosProviderHouse; | ||||
|  | ||||
|         public FileOcelotConfigurationCreator( | ||||
|             IOptions<FileConfiguration> options,  | ||||
| @@ -38,10 +40,14 @@ namespace Ocelot.Configuration.Creator | ||||
|             IClaimToThingConfigurationParser claimToThingConfigurationParser,  | ||||
|             ILogger<FileOcelotConfigurationCreator> logger, | ||||
|             ILoadBalancerFactory loadBalancerFactory, | ||||
|             ILoadBalancerHouse loadBalancerHouse) | ||||
|             ILoadBalancerHouse loadBalancerHouse,  | ||||
|             IQoSProviderFactory qoSProviderFactory,  | ||||
|             IQosProviderHouse qosProviderHouse) | ||||
|         { | ||||
|             _loadBalanceFactory = loadBalancerFactory; | ||||
|             _loadBalancerHouse = loadBalancerHouse; | ||||
|             _qoSProviderFactory = qoSProviderFactory; | ||||
|             _qosProviderHouse = qosProviderHouse; | ||||
|             _options = options; | ||||
|             _configurationValidator = configurationValidator; | ||||
|             _claimToThingConfigurationParser = claimToThingConfigurationParser; | ||||
| @@ -86,16 +92,17 @@ namespace Ocelot.Configuration.Creator | ||||
|         { | ||||
|             var isAuthenticated = IsAuthenticated(fileReRoute); | ||||
|  | ||||
|             var isAuthorised = IsAuthenticated(fileReRoute); | ||||
|             var isAuthorised = IsAuthorised(fileReRoute); | ||||
|  | ||||
|             var isCached = IsCached(fileReRoute); | ||||
|  | ||||
|             var requestIdKey = BuildRequestId(fileReRoute, globalConfiguration); | ||||
|  | ||||
|             var loadBalancerKey = BuildLoadBalancerKey(fileReRoute); | ||||
|             var reRouteKey = BuildReRouteKey(fileReRoute); | ||||
|  | ||||
|             var upstreamTemplatePattern = BuildUpstreamTemplate(fileReRoute); | ||||
|             var isQos = fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions.TimeoutValue >0; | ||||
|             var upstreamTemplatePattern = BuildUpstreamTemplatePattern(fileReRoute); | ||||
|  | ||||
|             var isQos = IsQoS(fileReRoute); | ||||
|  | ||||
|             var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration); | ||||
|  | ||||
| @@ -103,11 +110,12 @@ namespace Ocelot.Configuration.Creator | ||||
|  | ||||
|             var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest); | ||||
|   | ||||
|  | ||||
|             var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest); | ||||
|  | ||||
|             var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest); | ||||
|  | ||||
|             var qosOptions = BuildQoSOptions(fileReRoute); | ||||
|  | ||||
|             var reRoute = new ReRouteBuilder() | ||||
|                 .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) | ||||
|                 .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) | ||||
| @@ -127,14 +135,31 @@ namespace Ocelot.Configuration.Creator | ||||
|                 .WithLoadBalancer(fileReRoute.LoadBalancer) | ||||
|                 .WithDownstreamHost(fileReRoute.DownstreamHost) | ||||
|                 .WithDownstreamPort(fileReRoute.DownstreamPort) | ||||
|                 .WithLoadBalancerKey(loadBalancerKey) | ||||
|                 .WithLoadBalancerKey(reRouteKey) | ||||
|                 .WithServiceProviderConfiguraion(serviceProviderConfiguration) | ||||
|                 .WithIsQos(isQos) | ||||
|                 .WithQosOptions(qosOptions) | ||||
|                 .Build();    | ||||
|  | ||||
|             await SetupLoadBalancer(reRoute); | ||||
|             SetupQosProvider(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) | ||||
|         { | ||||
|             return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); | ||||
| @@ -161,7 +186,7 @@ namespace Ocelot.Configuration.Creator | ||||
|                 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 | ||||
|             var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}{fileReRoute.UpstreamHttpMethod}"; | ||||
| @@ -183,7 +208,13 @@ namespace Ocelot.Configuration.Creator | ||||
|         private async Task SetupLoadBalancer(ReRoute 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) | ||||
| @@ -204,7 +235,7 @@ namespace Ocelot.Configuration.Creator | ||||
|                     .Build(); | ||||
|         } | ||||
|  | ||||
|         private string BuildUpstreamTemplate(FileReRoute reRoute) | ||||
|         private string BuildUpstreamTemplatePattern(FileReRoute reRoute) | ||||
|         { | ||||
|             var upstreamTemplate = reRoute.UpstreamPathTemplate; | ||||
|  | ||||
| @@ -228,6 +259,11 @@ namespace Ocelot.Configuration.Creator | ||||
|                 upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything); | ||||
|             } | ||||
|  | ||||
|             if (upstreamTemplate == "/") | ||||
|             { | ||||
|                 return RegExForwardSlashOnly; | ||||
|             } | ||||
|  | ||||
|             var route = reRoute.ReRouteIsCaseSensitive  | ||||
|                 ? $"{upstreamTemplate}{RegExMatchEndString}"  | ||||
|                 : $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; | ||||
|   | ||||
| @@ -1,14 +1,15 @@ | ||||
| using Polly.Timeout; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
| using System; | ||||
| using Polly.Timeout; | ||||
|  | ||||
| namespace Ocelot.Configuration | ||||
| { | ||||
|     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; | ||||
|             DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak); | ||||
|   | ||||
| @@ -25,12 +25,12 @@ namespace Ocelot.Configuration | ||||
|             string loadBalancer,  | ||||
|             string downstreamHost,  | ||||
|             int downstreamPort,  | ||||
|             string loadBalancerKey,  | ||||
|             string reRouteKey,  | ||||
|             ServiceProviderConfiguraion serviceProviderConfiguraion, | ||||
|             bool isQos, | ||||
|             QoSOptions qos) | ||||
|         { | ||||
|             LoadBalancerKey = loadBalancerKey; | ||||
|             ReRouteKey = reRouteKey; | ||||
|             ServiceProviderConfiguraion = serviceProviderConfiguraion; | ||||
|             LoadBalancer = loadBalancer; | ||||
|             DownstreamHost = downstreamHost; | ||||
| @@ -57,7 +57,7 @@ namespace Ocelot.Configuration | ||||
|             QosOptions = qos; | ||||
|         } | ||||
|  | ||||
|         public string LoadBalancerKey {get;private set;} | ||||
|         public string ReRouteKey {get;private set;} | ||||
|         public PathTemplate DownstreamPathTemplate { get; private set; } | ||||
|         public PathTemplate UpstreamPathTemplate { get; private set; } | ||||
|         public string UpstreamTemplatePattern { get; private set; } | ||||
|   | ||||
| @@ -28,6 +28,7 @@ using Ocelot.Logging; | ||||
| using Ocelot.QueryStrings; | ||||
| using Ocelot.Request.Builder; | ||||
| using Ocelot.Requester; | ||||
| using Ocelot.Requester.QoS; | ||||
| using Ocelot.Responder; | ||||
| using Ocelot.ServiceDiscovery; | ||||
|  | ||||
| @@ -61,6 +62,8 @@ namespace Ocelot.DependencyInjection | ||||
|         { | ||||
|             services.AddMvcCore().AddJsonFormatters(); | ||||
|             services.AddLogging(); | ||||
|             services.AddSingleton<IQosProviderHouse, QosProviderHouse>(); | ||||
|             services.AddSingleton<IQoSProviderFactory, QoSProviderFactory>(); | ||||
|             services.AddSingleton<IServiceDiscoveryProviderFactory, ServiceDiscoveryProviderFactory>(); | ||||
|             services.AddSingleton<ILoadBalancerFactory, LoadBalancerFactory>(); | ||||
|             services.AddSingleton<ILoadBalancerHouse, LoadBalancerHouse>(); | ||||
|   | ||||
| @@ -30,6 +30,13 @@ namespace Ocelot.DownstreamRouteFinder.Finder | ||||
|  | ||||
|             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); | ||||
|  | ||||
|                 if (urlMatch.Data.Match) | ||||
|   | ||||
| @@ -25,6 +25,8 @@ | ||||
|         ServicesAreNullError, | ||||
|         ServicesAreEmptyError, | ||||
|         UnableToFindServiceDiscoveryProviderError, | ||||
|         UnableToFindLoadBalancerError | ||||
|         UnableToFindLoadBalancerError, | ||||
|         RequestTimedOutError, | ||||
|         UnableToFindQoSProviderError | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -6,7 +6,6 @@ using Ocelot.LoadBalancer.LoadBalancers; | ||||
| using Ocelot.Logging; | ||||
| using Ocelot.Middleware; | ||||
| using Ocelot.QueryStrings.Middleware; | ||||
| using Ocelot.ServiceDiscovery; | ||||
|  | ||||
| namespace Ocelot.LoadBalancer.Middleware | ||||
| { | ||||
| @@ -31,7 +30,7 @@ namespace Ocelot.LoadBalancer.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) | ||||
|             { | ||||
|                 SetPipelineError(loadBalancer.Errors); | ||||
|   | ||||
| @@ -3,6 +3,7 @@ using System.Threading.Tasks; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Ocelot.Responses; | ||||
| using Ocelot.Configuration; | ||||
| using Ocelot.Requester.QoS; | ||||
|  | ||||
| namespace Ocelot.Request.Builder | ||||
| { | ||||
| @@ -18,7 +19,7 @@ namespace Ocelot.Request.Builder | ||||
|             string contentType,  | ||||
|             RequestId.RequestId requestId, | ||||
|             bool isQos, | ||||
|             QoSOptions qos) | ||||
|             IQoSProvider qosProvider) | ||||
|         { | ||||
|             var request = await new RequestBuilder() | ||||
|                 .WithHttpMethod(httpMethod) | ||||
| @@ -30,7 +31,7 @@ namespace Ocelot.Request.Builder | ||||
|                 .WithRequestId(requestId) | ||||
|                 .WithCookies(cookies) | ||||
|                 .WithIsQos(isQos) | ||||
|                 .WithQos(qos) | ||||
|                 .WithQos(qosProvider) | ||||
|                 .Build(); | ||||
|  | ||||
|             return new OkResponse<Request>(request); | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| using System.IO; | ||||
| using System.Threading.Tasks; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Ocelot.Requester.QoS; | ||||
| using Ocelot.Responses; | ||||
| using Ocelot.Configuration; | ||||
|  | ||||
| namespace Ocelot.Request.Builder | ||||
| { | ||||
| @@ -17,6 +17,6 @@ namespace Ocelot.Request.Builder | ||||
|             string contentType, | ||||
|             RequestId.RequestId requestId, | ||||
|             bool isQos, | ||||
|             QoSOptions qos); | ||||
|             IQoSProvider qosProvider); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,7 @@ using System.Net.Http.Headers; | ||||
| using System.Threading.Tasks; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.Extensions.Primitives; | ||||
| using Ocelot.Configuration; | ||||
| using Ocelot.Requester.QoS; | ||||
|  | ||||
| namespace Ocelot.Request.Builder | ||||
| { | ||||
| @@ -24,7 +24,7 @@ namespace Ocelot.Request.Builder | ||||
|         private IRequestCookieCollection _cookies; | ||||
|         private readonly string[] _unsupportedHeaders = {"host"}; | ||||
|         private bool _isQos; | ||||
|         private QoSOptions _qos; | ||||
|         private IQoSProvider _qoSProvider; | ||||
|  | ||||
|         public RequestBuilder WithHttpMethod(string httpMethod) | ||||
|         { | ||||
| @@ -80,9 +80,9 @@ namespace Ocelot.Request.Builder | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         public RequestBuilder WithQos(QoSOptions qos) | ||||
|         public RequestBuilder WithQos(IQoSProvider qoSProvider) | ||||
|         { | ||||
|             _qos = qos; | ||||
|             _qoSProvider = qoSProvider; | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
| @@ -105,7 +105,7 @@ namespace Ocelot.Request.Builder | ||||
|  | ||||
|             var cookieContainer = CreateCookieContainer(uri); | ||||
|  | ||||
|             return new Request(httpRequestMessage, cookieContainer,_isQos,_qos); | ||||
|             return new Request(httpRequestMessage, cookieContainer,_isQos, _qoSProvider); | ||||
|         } | ||||
|  | ||||
|         private Uri CreateUri() | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| using System.Threading.Tasks; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using Ocelot.Infrastructure.RequestData; | ||||
| using Ocelot.Logging; | ||||
| using Ocelot.Middleware; | ||||
| using Ocelot.Request.Builder; | ||||
| using Ocelot.Requester.QoS; | ||||
|  | ||||
| namespace Ocelot.Request.Middleware | ||||
| { | ||||
| @@ -13,15 +13,18 @@ namespace Ocelot.Request.Middleware | ||||
|         private readonly RequestDelegate _next; | ||||
|         private readonly IRequestCreator _requestCreator; | ||||
|         private readonly IOcelotLogger _logger; | ||||
|         private readonly IQosProviderHouse _qosProviderHouse; | ||||
|  | ||||
|         public HttpRequestBuilderMiddleware(RequestDelegate next, | ||||
|             IOcelotLoggerFactory loggerFactory, | ||||
|             IRequestScopedDataRepository requestScopedDataRepository,  | ||||
|             IRequestCreator requestCreator) | ||||
|             IRequestCreator requestCreator,  | ||||
|             IQosProviderHouse qosProviderHouse) | ||||
|             :base(requestScopedDataRepository) | ||||
|         { | ||||
|             _next = next; | ||||
|             _requestCreator = requestCreator; | ||||
|             _qosProviderHouse = qosProviderHouse; | ||||
|             _logger = loggerFactory.CreateLogger<HttpRequestBuilderMiddleware>(); | ||||
|         } | ||||
|  | ||||
| @@ -29,17 +32,35 @@ namespace Ocelot.Request.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 | ||||
|                 .Build(context.Request.Method, DownstreamUrl, context.Request.Body, | ||||
|                     context.Request.Headers, context.Request.Cookies, context.Request.QueryString, | ||||
|                     context.Request.ContentType, new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier), | ||||
|                     DownstreamRoute.ReRoute.IsQos,DownstreamRoute.ReRoute.QosOptions); | ||||
|                 .Build(context.Request.Method, | ||||
|                     DownstreamUrl, | ||||
|                     context.Request.Body, | ||||
|                     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) | ||||
|             { | ||||
|                 _logger.LogDebug("IRequestCreator returned an error, setting pipeline error"); | ||||
|  | ||||
|                 SetPipelineError(buildResult.Errors); | ||||
|  | ||||
|                 return; | ||||
|             } | ||||
|             _logger.LogDebug("setting upstream request"); | ||||
|   | ||||
| @@ -2,22 +2,27 @@ | ||||
| using Ocelot.Values; | ||||
| using System.Net; | ||||
| using System.Net.Http; | ||||
| using Ocelot.Requester.QoS; | ||||
|  | ||||
| namespace Ocelot.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; | ||||
|             CookieContainer = cookieContainer; | ||||
|             IsQos = isQos; | ||||
|             Qos = qos; | ||||
|             QosProvider = qosProvider; | ||||
|         } | ||||
|  | ||||
|         public HttpRequestMessage HttpRequestMessage { get; private set; } | ||||
|         public CookieContainer CookieContainer { 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 Ocelot.Logging; | ||||
| using Ocelot.Values; | ||||
| using Polly.Timeout; | ||||
| using System; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Net.Http; | ||||
| using System.Threading.Tasks; | ||||
| using Ocelot.Logging; | ||||
| using Ocelot.Requester.QoS; | ||||
|  | ||||
| namespace Ocelot.Requester | ||||
| { | ||||
|     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; | ||||
|         } | ||||
|  | ||||
|         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() | ||||
|         { | ||||
|             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(); | ||||
|                 delegatingHandler.InnerHandler = httpMessageHandler; | ||||
|   | ||||
| @@ -1,17 +1,17 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Net.Http; | ||||
| using System.Threading.Tasks; | ||||
| using Ocelot.Errors; | ||||
| using Ocelot.Responses; | ||||
| using Ocelot.Logging; | ||||
| using Ocelot.Responses; | ||||
| using Polly.CircuitBreaker; | ||||
| using Polly.Timeout; | ||||
|  | ||||
| namespace Ocelot.Requester | ||||
| { | ||||
|     public class HttpClientHttpRequester : IHttpRequester | ||||
|     { | ||||
|         private readonly IOcelotLogger _logger; | ||||
|   | ||||
|  | ||||
|         public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory) | ||||
|         { | ||||
|             _logger = loggerFactory.CreateLogger<HttpClientHttpRequester>(); | ||||
| @@ -19,14 +19,15 @@ namespace Ocelot.Requester | ||||
|  | ||||
|         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 }) | ||||
|             { | ||||
|                 if (request.IsQos) | ||||
|                 { | ||||
|                     builder.WithCircuitBreaker(request.Qos, _logger, handler); | ||||
|                     builder.WithQoS(request.QosProvider, _logger, handler); | ||||
|                 }            | ||||
|  | ||||
|                 using (var httpClient = builder.Build(handler)) | ||||
|                 { | ||||
|                     try | ||||
| @@ -34,13 +35,19 @@ namespace Ocelot.Requester | ||||
|                         var response = await httpClient.SendAsync(request.HttpRequestMessage); | ||||
|                         return new OkResponse<HttpResponseMessage>(response); | ||||
|                     } | ||||
|                     catch (Exception exception) | ||||
|                     catch (TimeoutRejectedException exception) | ||||
|                     { | ||||
|                         return | ||||
|                             new ErrorResponse<HttpResponseMessage>(new List<Error> | ||||
|                             { | ||||
|                                 new UnableToCompleteRequestError(exception) | ||||
|                             }); | ||||
|                             new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception)); | ||||
|                     } | ||||
|                     catch (BrokenCircuitException exception) | ||||
|                     { | ||||
|                         return | ||||
|                             new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception)); | ||||
|                     } | ||||
|                     catch (Exception exception) | ||||
|                     { | ||||
|                         return new ErrorResponse<HttpResponseMessage>(new UnableToCompleteRequestError(exception)); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -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); | ||||
|             } | ||||
|  | ||||
|             if (errors.Any(e => e.Code == OcelotErrorCode.RequestTimedOutError)) | ||||
|             { | ||||
|                 return new OkResponse<int>(503); | ||||
|             } | ||||
|  | ||||
|             return new OkResponse<int>(404); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -5,7 +5,13 @@ namespace Ocelot.Responses | ||||
| { | ||||
|     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.Validator; | ||||
| using Ocelot.LoadBalancer.LoadBalancers; | ||||
| using Ocelot.Requester.QoS; | ||||
| using Ocelot.Responses; | ||||
| using Shouldly; | ||||
| using TestStack.BDDfy; | ||||
| @@ -28,9 +29,15 @@ namespace Ocelot.UnitTests.Configuration | ||||
|         private readonly Mock<ILoadBalancerFactory> _loadBalancerFactory; | ||||
|         private readonly Mock<ILoadBalancerHouse> _loadBalancerHouse; | ||||
|         private readonly Mock<ILoadBalancer> _loadBalancer; | ||||
|         private readonly Mock<IQoSProviderFactory> _qosProviderFactory; | ||||
|         private readonly Mock<IQosProviderHouse> _qosProviderHouse; | ||||
|         private readonly Mock<IQoSProvider> _qosProvider; | ||||
|  | ||||
|         public FileConfigurationCreatorTests() | ||||
|         { | ||||
|             _qosProviderFactory = new Mock<IQoSProviderFactory>(); | ||||
|             _qosProviderHouse = new Mock<IQosProviderHouse>(); | ||||
|             _qosProvider = new Mock<IQoSProvider>(); | ||||
|             _logger = new Mock<ILogger<FileOcelotConfigurationCreator>>(); | ||||
|             _configParser = new Mock<IClaimToThingConfigurationParser>(); | ||||
|             _validator = new Mock<IConfigurationValidator>(); | ||||
| @@ -40,7 +47,8 @@ namespace Ocelot.UnitTests.Configuration | ||||
|             _loadBalancer = new Mock<ILoadBalancer>(); | ||||
|             _ocelotConfigurationCreator = new FileOcelotConfigurationCreator(  | ||||
|                 _fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object, | ||||
|                 _loadBalancerFactory.Object, _loadBalancerHouse.Object); | ||||
|                 _loadBalancerFactory.Object, _loadBalancerHouse.Object,  | ||||
|                 _qosProviderFactory.Object, _qosProviderHouse.Object); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
| @@ -64,10 +72,39 @@ namespace Ocelot.UnitTests.Configuration | ||||
|                                 .When(x => x.WhenICreateTheConfig()) | ||||
|                                 .Then(x => x.TheLoadBalancerFactoryIsCalledCorrectly()) | ||||
|                                 .And(x => x.ThenTheLoadBalancerHouseIsCalledCorrectly()) | ||||
|                                 | ||||
|                     .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] | ||||
|         public void should_use_downstream_host() | ||||
|         { | ||||
| @@ -164,11 +201,11 @@ namespace Ocelot.UnitTests.Configuration | ||||
|                                     .WithUpstreamPathTemplate("/api/products/{productId}") | ||||
|                                     .WithUpstreamHttpMethod("Get") | ||||
|                                     .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") | ||||
|                                     .WithServiceName("ProductService") | ||||
|                                     .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder() | ||||
|                                         .WithUseServiceDiscovery(true) | ||||
|                                         .WithServiceDiscoveryProvider("consul") | ||||
|                                         .WithServiceDiscoveryProviderHost("127.0.0.1") | ||||
|                                         .WithServiceName("ProductService") | ||||
|                                         .Build()) | ||||
|                                     .Build() | ||||
|                             })) | ||||
| @@ -568,7 +605,7 @@ namespace Ocelot.UnitTests.Configuration | ||||
|                         .WithDownstreamPathTemplate("/api/products/") | ||||
|                         .WithUpstreamPathTemplate("/") | ||||
|                         .WithUpstreamHttpMethod("Get") | ||||
|                         .WithUpstreamTemplatePattern("/$") | ||||
|                         .WithUpstreamTemplatePattern("^/$") | ||||
|                         .Build() | ||||
|                 })) | ||||
|                 .BDDfy(); | ||||
| @@ -643,5 +680,24 @@ namespace Ocelot.UnitTests.Configuration | ||||
|             _loadBalancerHouse | ||||
|                 .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] | ||||
|         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")) | ||||
|                 .And( | ||||
| @@ -61,7 +92,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder | ||||
|                             .WithUpstreamHttpMethod("Get") | ||||
|                             .Build() | ||||
|                         ))) | ||||
|                 .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) | ||||
|                 .And(x => x.ThenTheUrlMatcherIsNotCalled()) | ||||
|                 .BDDfy(); | ||||
|         } | ||||
|  | ||||
| @@ -105,7 +136,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder | ||||
|         [Fact] | ||||
|         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> | ||||
|                      { | ||||
|                         new ReRouteBuilder() | ||||
| @@ -148,6 +179,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder | ||||
|                 .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) | ||||
|         { | ||||
|             _match = match; | ||||
|   | ||||
| @@ -18,6 +18,26 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher | ||||
|             _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] | ||||
|         public void should_find_match_when_template_smaller_than_valid_path() | ||||
|         { | ||||
|   | ||||
| @@ -20,6 +20,7 @@ using Ocelot.Responses; | ||||
| using TestStack.BDDfy; | ||||
| using Xunit; | ||||
| using Ocelot.Configuration; | ||||
| using Ocelot.Requester.QoS; | ||||
|  | ||||
| namespace Ocelot.UnitTests.Request | ||||
| { | ||||
| @@ -27,6 +28,7 @@ namespace Ocelot.UnitTests.Request | ||||
|     { | ||||
|         private readonly Mock<IRequestCreator> _requestBuilder; | ||||
|         private readonly Mock<IRequestScopedDataRepository> _scopedRepository; | ||||
|         private readonly Mock<IQosProviderHouse> _qosProviderHouse; | ||||
|         private readonly string _url; | ||||
|         private readonly TestServer _server; | ||||
|         private readonly HttpClient _client; | ||||
| @@ -38,6 +40,7 @@ namespace Ocelot.UnitTests.Request | ||||
|         public HttpRequestBuilderMiddlewareTests() | ||||
|         { | ||||
|             _url = "http://localhost:51879"; | ||||
|             _qosProviderHouse = new Mock<IQosProviderHouse>(); | ||||
|             _requestBuilder = new Mock<IRequestCreator>(); | ||||
|             _scopedRepository = new Mock<IRequestScopedDataRepository>(); | ||||
|             var builder = new WebHostBuilder() | ||||
| @@ -45,6 +48,7 @@ namespace Ocelot.UnitTests.Request | ||||
|               { | ||||
|                   x.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>(); | ||||
|                   x.AddLogging(); | ||||
|                   x.AddSingleton(_qosProviderHouse.Object); | ||||
|                   x.AddSingleton(_requestBuilder.Object); | ||||
|                   x.AddSingleton(_scopedRepository.Object); | ||||
|               }) | ||||
| @@ -72,15 +76,22 @@ namespace Ocelot.UnitTests.Request | ||||
|                     .WithUpstreamHttpMethod("Get") | ||||
|                     .Build()); | ||||
|  | ||||
|  | ||||
|             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.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()) | ||||
|                 .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) | ||||
|                 .BDDfy(); | ||||
|         } | ||||
|  | ||||
|         private void GivenTheQosProviderHouseReturns(Response<IQoSProvider> qosProvider) | ||||
|         { | ||||
|             _qosProviderHouse | ||||
|                 .Setup(x => x.Get(It.IsAny<string>())) | ||||
|                 .Returns(qosProvider); | ||||
|         } | ||||
|  | ||||
|         private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) | ||||
|         { | ||||
|             _downstreamRoute = new OkResponse<DownstreamRoute>(downstreamRoute); | ||||
| @@ -94,7 +105,7 @@ namespace Ocelot.UnitTests.Request | ||||
|             _request = new OkResponse<Ocelot.Request.Request>(request); | ||||
|             _requestBuilder | ||||
|                 .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); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -11,6 +11,7 @@ using Shouldly; | ||||
| using TestStack.BDDfy; | ||||
| using Xunit; | ||||
| using Ocelot.Configuration; | ||||
| using Ocelot.Requester.QoS; | ||||
|  | ||||
| namespace Ocelot.UnitTests.Request | ||||
| { | ||||
| @@ -27,7 +28,7 @@ namespace Ocelot.UnitTests.Request | ||||
|         private Response<Ocelot.Request.Request> _result; | ||||
|         private Ocelot.RequestId.RequestId _requestId; | ||||
|         private bool _isQos; | ||||
|         private QoSOptions _qos; | ||||
|         private IQoSProvider _qoSProvider; | ||||
|  | ||||
|         public RequestBuilderTests() | ||||
|         { | ||||
| @@ -40,7 +41,7 @@ namespace Ocelot.UnitTests.Request | ||||
|         { | ||||
|             this.Given(x => x.GivenIHaveHttpMethod("GET")) | ||||
|                 .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()) | ||||
|                 .And(x => x.ThenTheCorrectDownstreamUrlIsUsed("http://www.bbc.co.uk/")) | ||||
|                 .BDDfy(); | ||||
| @@ -51,7 +52,7 @@ namespace Ocelot.UnitTests.Request | ||||
|         { | ||||
|             this.Given(x => x.GivenIHaveHttpMethod("POST")) | ||||
|                 .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()) | ||||
|                 .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.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) | ||||
|                 .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()) | ||||
|                .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.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) | ||||
|                 .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()) | ||||
|                .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.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) | ||||
|                 .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()) | ||||
|                .And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary | ||||
| @@ -119,7 +120,7 @@ namespace Ocelot.UnitTests.Request | ||||
|                 { | ||||
|                     {"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()) | ||||
|                 .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.GivenTheHttpHeadersAre(new HeaderDictionary())) | ||||
|                 .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()) | ||||
|                 .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary | ||||
|                 { | ||||
| @@ -157,7 +158,7 @@ namespace Ocelot.UnitTests.Request | ||||
|                     {"RequestId", "534534gv54gv45g" } | ||||
|                 })) | ||||
|                 .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()) | ||||
|                 .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.GivenTheHttpHeadersAre(new HeaderDictionary())) | ||||
|                 .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()) | ||||
|                 .And(x => x.ThenTheRequestIdIsNotInTheHeaders()) | ||||
|                 .BDDfy(); | ||||
| @@ -188,10 +189,10 @@ namespace Ocelot.UnitTests.Request | ||||
|             _requestId = requestId; | ||||
|         } | ||||
|  | ||||
|         private void GivenTheQos(bool isQos, QoSOptions qos) | ||||
|         private void GivenTheQos(bool isQos, IQoSProvider qoSProvider) | ||||
|         { | ||||
|             _isQos = isQos; | ||||
|             _qos = qos; | ||||
|             _qoSProvider = qoSProvider; | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
| @@ -304,7 +305,7 @@ namespace Ocelot.UnitTests.Request | ||||
|         private void WhenICreateARequest() | ||||
|         { | ||||
|             _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.Requester; | ||||
| using Ocelot.Requester.Middleware; | ||||
| using Ocelot.Requester.QoS; | ||||
| using Ocelot.Responder; | ||||
| using Ocelot.Responses; | ||||
| using TestStack.BDDfy; | ||||
| @@ -61,7 +62,7 @@ namespace Ocelot.UnitTests.Requester | ||||
|         [Fact] | ||||
|         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.GivenTheScopedRepoReturns()) | ||||
|                 .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.Middleware; | ||||
| using Ocelot.Requester; | ||||
| using Ocelot.Responder; | ||||
| using Ocelot.Responses; | ||||
| using Shouldly; | ||||
| @@ -20,6 +22,18 @@ namespace Ocelot.UnitTests.Responder | ||||
|             _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] | ||||
|         public void should_create_unauthenticated_response_code() | ||||
|         { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Tom Pallister
					Tom Pallister