diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index a038c877..00000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 1a7759c2..f96b1d02 100644 --- a/.gitignore +++ b/.gitignore @@ -236,3 +236,9 @@ _Pvt_Extensions # FAKE - F# Make .fake/ tools/ + +# MacOS +.DS_Store + +# Ocelot acceptance test config +test/Ocelot.AcceptanceTests/configuration.json \ No newline at end of file diff --git a/README.md b/README.md index 738ffbe1..dfbdfd1e 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ Ocelot's primary functionality is to take incomeing http requests and forward th to a downstream service. At the moment in the form of another http request (in the future this could be any transport mechanism.). -Ocelot always adds a trailing slash to an UpstreamTemplate. +Ocelot always adds a trailing slash to an UpstreamPathTemplate. Ocelot's describes the routing of one request to another as a ReRoute. In order to get anything working in Ocelot you need to set up a ReRoute in the configuration. @@ -140,16 +140,16 @@ the following. "DownstreamScheme": "https", "DownstreamPort": 80, "DownstreamHost" "localhost" - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Put" } The DownstreamPathTemplate,Scheme, Port and Host make the URL that this request will be forwarded to. -The UpstreamTemplate is the URL that Ocelot will use to identity which +The UpstreamPathTemplate is the URL that Ocelot will use to identity which DownstreamPathTemplate to use for a given request. Finally the UpstreamHttpMethod is used so Ocelot can distinguish between requests to the same URL and is obviously needed to work :) In Ocelot you can add placeholders for variables to your Templates in the form of {something}. -The placeholder needs to be in both the DownstreamPathTemplate and UpstreamTemplate. If it is +The placeholder needs to be in both the DownstreamPathTemplate and UpstreamPathTemplate. If it is Ocelot will attempt to replace the placeholder with the correct variable value from the Upstream URL when the request comes in. @@ -190,7 +190,7 @@ and LeastConnection algorithm you can use. If no load balancer is specified Ocel { "DownstreamPathTemplate": "/api/posts/{postId}", "DownstreamScheme": "https", - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Put", "ServiceName": "product" "LoadBalancer": "LeastConnection" @@ -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 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 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... diff --git a/build.cake b/build.cake index 1a1f005d..579f4e82 100644 --- a/build.cake +++ b/build.cake @@ -42,7 +42,7 @@ var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package"; // internal build variables - don't change these. var releaseTag = ""; -var committedVersion = "0.0.0-dev"; +string committedVersion = "0.0.0-dev"; var buildVersion = committedVersion; var target = Argument("target", "Default"); @@ -310,7 +310,8 @@ private void PublishPackages(string feedApiKey, string codeFeedUrl, string symbo .ToDictionary(v => v[0], v => v[1]); var codePackage = packagesDir + File(artifacts["nuget"]); - var symbolsPackage = packagesDir + File(artifacts["nugetSymbols"]); + + Information("Pushing package"); NuGetPush( codePackage, @@ -318,14 +319,6 @@ private void PublishPackages(string feedApiKey, string codeFeedUrl, string symbo ApiKey = feedApiKey, Source = codeFeedUrl }); - - NuGetPush( - symbolsPackage, - new NuGetPushSettings { - ApiKey = feedApiKey, - Source = symbolFeedUrl - }); - } /// gets the resource from the specified url diff --git a/configuration-explanation.txt b/configuration-explanation.txt index b6db84a2..d7b4077f 100644 --- a/configuration-explanation.txt +++ b/configuration-explanation.txt @@ -15,7 +15,7 @@ # The path template we are listening on for this re route, Ocelot will add a trailing # slash to this property. Then when a request is made Ocelot makes sure a trailing # slash is added, so everything matches - "UpstreamTemplate": "/identityserverexample", + "UpstreamPathTemplate": "/identityserverexample", # The method we are listening for on this re route "UpstreamHttpMethod": "Get", # Only support identity server at the moment diff --git a/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs b/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs index 65260d64..67f2ebbd 100644 --- a/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs +++ b/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs @@ -12,7 +12,7 @@ namespace Ocelot.Authentication.Handler.Creator /// public class AuthenticationHandlerCreator : IAuthenticationHandlerCreator { - public Response CreateIdentityServerAuthenticationHandler(IApplicationBuilder app, AuthenticationOptions authOptions) + public Response Create(IApplicationBuilder app, AuthenticationOptions authOptions) { var builder = app.New(); diff --git a/src/Ocelot/Authentication/Handler/Creator/IAuthenticationHandlerCreator.cs b/src/Ocelot/Authentication/Handler/Creator/IAuthenticationHandlerCreator.cs index 6baa0385..9d92c81d 100644 --- a/src/Ocelot/Authentication/Handler/Creator/IAuthenticationHandlerCreator.cs +++ b/src/Ocelot/Authentication/Handler/Creator/IAuthenticationHandlerCreator.cs @@ -8,6 +8,6 @@ namespace Ocelot.Authentication.Handler.Creator public interface IAuthenticationHandlerCreator { - Response CreateIdentityServerAuthenticationHandler(IApplicationBuilder app, AuthenticationOptions authOptions); + Response Create(IApplicationBuilder app, AuthenticationOptions authOptions); } } diff --git a/src/Ocelot/Authentication/Handler/Factory/AuthenticationHandlerFactory.cs b/src/Ocelot/Authentication/Handler/Factory/AuthenticationHandlerFactory.cs index 6379cc1f..60253816 100644 --- a/src/Ocelot/Authentication/Handler/Factory/AuthenticationHandlerFactory.cs +++ b/src/Ocelot/Authentication/Handler/Factory/AuthenticationHandlerFactory.cs @@ -19,7 +19,7 @@ namespace Ocelot.Authentication.Handler.Factory public Response Get(IApplicationBuilder app, AuthenticationOptions authOptions) { - var handler = _creator.CreateIdentityServerAuthenticationHandler(app, authOptions); + var handler = _creator.Create(app, authOptions); if (!handler.IsError) { diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs index ad30e166..037deeee 100644 --- a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs +++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using Ocelot.Authentication.Handler.Factory; using Ocelot.Configuration; using Ocelot.Errors; diff --git a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs index cb7849e9..96f7acd6 100644 --- a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs +++ b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using System.Security.Claims; using Ocelot.Errors; using Ocelot.Responses; diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs index 547fc7f7..a86643a4 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Logging; -using Ocelot.Infrastructure.RequestData; +using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Responses; @@ -61,7 +60,7 @@ namespace Ocelot.Authorisation.Middleware SetPipelineError(new List { new UnauthorisedError( - $"{context.User.Identity.Name} unable to access {DownstreamRoute.ReRoute.UpstreamTemplate}") + $"{context.User.Identity.Name} unable to access {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}") }); } } diff --git a/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs b/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs new file mode 100644 index 00000000..61d5e847 --- /dev/null +++ b/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; + +namespace Ocelot.Configuration.Builder +{ + public class AuthenticationOptionsBuilder + { + + private string _provider; + private string _providerRootUrl; + private string _scopeName; + private string _scopeSecret; + private bool _requireHttps; + private List _additionalScopes; + + public AuthenticationOptionsBuilder WithProvider(string provider) + { + _provider = provider; + return this; + } + + public AuthenticationOptionsBuilder WithProviderRootUrl(string providerRootUrl) + { + _providerRootUrl = providerRootUrl; + return this; + } + + public AuthenticationOptionsBuilder WithScopeName(string scopeName) + { + _scopeName = scopeName; + return this; + } + + public AuthenticationOptionsBuilder WithScopeSecret(string scopeSecret) + { + _scopeSecret = scopeSecret; + return this; + } + + public AuthenticationOptionsBuilder WithRequireHttps(bool requireHttps) + { + _requireHttps = requireHttps; + return this; + } + + public AuthenticationOptionsBuilder WithAdditionalScopes(List additionalScopes) + { + _additionalScopes = additionalScopes; + return this; + } + + public AuthenticationOptions Build() + { + return new AuthenticationOptions(_provider, _providerRootUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs b/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs new file mode 100644 index 00000000..8d953d5c --- /dev/null +++ b/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs @@ -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); + } + } +} diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 568e7c68..382cf374 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -1,23 +1,19 @@ using System; using System.Collections.Generic; +using System.Net.Http; using Ocelot.Values; namespace Ocelot.Configuration.Builder { public class ReRouteBuilder { + private AuthenticationOptions _authenticationOptions; private string _loadBalancerKey; private string _downstreamPathTemplate; private string _upstreamTemplate; private string _upstreamTemplatePattern; private string _upstreamHttpMethod; private bool _isAuthenticated; - private string _authenticationProvider; - private string _authenticationProviderUrl; - private string _scopeName; - private List _additionalScopes; - private bool _requireHttps; - private string _scopeSecret; private List _configHeaderExtractorProperties; private List _claimToClaims; private Dictionary _routeClaimRequirement; @@ -26,25 +22,14 @@ namespace Ocelot.Configuration.Builder private string _requestIdHeaderKey; private bool _isCached; private CacheOptions _fileCacheOptions; - private bool _useServiceDiscovery; - private string _serviceName; - private string _serviceDiscoveryProvider; - private string _serviceDiscoveryAddress; private string _downstreamScheme; private string _downstreamHost; - private int _dsPort; + private int _downstreamPort; private string _loadBalancer; - private string _serviceProviderHost; - private int _serviceProviderPort; + private ServiceProviderConfiguraion _serviceProviderConfiguraion; private bool _useQos; private QoSOptions _qosOptions; - - public ReRouteBuilder() - { - _additionalScopes = new List(); - } - public ReRouteBuilder WithLoadBalancer(string loadBalancer) { _loadBalancer = loadBalancer; @@ -63,37 +48,13 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithServiceDiscoveryAddress(string serviceDiscoveryAddress) - { - _serviceDiscoveryAddress = serviceDiscoveryAddress; - return this; - } - - public ReRouteBuilder WithServiceDiscoveryProvider(string serviceDiscoveryProvider) - { - _serviceDiscoveryProvider = serviceDiscoveryProvider; - return this; - } - - public ReRouteBuilder WithServiceName(string serviceName) - { - _serviceName = serviceName; - return this; - } - - public ReRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery) - { - _useServiceDiscovery = useServiceDiscovery; - return this; - } - public ReRouteBuilder WithDownstreamPathTemplate(string input) { _downstreamPathTemplate = input; return this; } - public ReRouteBuilder WithUpstreamTemplate(string input) + public ReRouteBuilder WithUpstreamPathTemplate(string input) { _upstreamTemplate = input; return this; @@ -121,42 +82,6 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithAuthenticationProvider(string input) - { - _authenticationProvider = input; - return this; - } - - public ReRouteBuilder WithAuthenticationProviderUrl(string input) - { - _authenticationProviderUrl = input; - return this; - } - - public ReRouteBuilder WithAuthenticationProviderScopeName(string input) - { - _scopeName = input; - return this; - } - - public ReRouteBuilder WithAuthenticationProviderAdditionalScopes(List input) - { - _additionalScopes = input; - return this; - } - - public ReRouteBuilder WithRequireHttps(bool input) - { - _requireHttps = input; - return this; - } - - public ReRouteBuilder WithScopeSecret(string input) - { - _scopeSecret = input; - return this; - } - public ReRouteBuilder WithRequestIdKey(string input) { _requestIdHeaderKey = input; @@ -201,7 +126,7 @@ namespace Ocelot.Configuration.Builder public ReRouteBuilder WithDownstreamPort(int port) { - _dsPort = port; + _downstreamPort = port; return this; } @@ -224,28 +149,43 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithServiceProviderHost(string serviceProviderHost) + public ReRouteBuilder WithServiceProviderConfiguraion(ServiceProviderConfiguraion serviceProviderConfiguraion) { - _serviceProviderHost = serviceProviderHost; + _serviceProviderConfiguraion = serviceProviderConfiguraion; return this; } - public ReRouteBuilder WithServiceProviderPort(int serviceProviderPort) + public ReRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions) { - _serviceProviderPort = serviceProviderPort; + _authenticationOptions = authenticationOptions; return this; } public ReRoute Build() { - return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, - _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, - _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, - _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _downstreamScheme, _loadBalancer, - _downstreamHost, _dsPort, _loadBalancerKey, new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _dsPort, _useServiceDiscovery, - _serviceDiscoveryProvider, _serviceProviderHost, _serviceProviderPort), - _useQos,_qosOptions); - + return new ReRoute( + new PathTemplate(_downstreamPathTemplate), + new PathTemplate(_upstreamTemplate), + new HttpMethod(_upstreamHttpMethod), + _upstreamTemplatePattern, + _isAuthenticated, + _authenticationOptions, + _configHeaderExtractorProperties, + _claimToClaims, + _routeClaimRequirement, + _isAuthorised, + _claimToQueries, + _requestIdHeaderKey, + _isCached, + _fileCacheOptions, + _downstreamScheme, + _loadBalancer, + _downstreamHost, + _downstreamPort, + _loadBalancerKey, + _serviceProviderConfiguraion, + _useQos, + _qosOptions); } } } diff --git a/src/Ocelot/Configuration/Builder/ServiceProviderConfiguraionBuilder.cs b/src/Ocelot/Configuration/Builder/ServiceProviderConfiguraionBuilder.cs new file mode 100644 index 00000000..129acae8 --- /dev/null +++ b/src/Ocelot/Configuration/Builder/ServiceProviderConfiguraionBuilder.cs @@ -0,0 +1,62 @@ +namespace Ocelot.Configuration.Builder +{ + public class ServiceProviderConfiguraionBuilder + { + private string _serviceName; + private string _downstreamHost; + private int _downstreamPort; + private bool _userServiceDiscovery; + private string _serviceDiscoveryProvider; + private string _serviceDiscoveryProviderHost; + private int _serviceDiscoveryProviderPort; + + public ServiceProviderConfiguraionBuilder WithServiceName(string serviceName) + { + _serviceName = serviceName; + return this; + } + + public ServiceProviderConfiguraionBuilder WithDownstreamHost(string downstreamHost) + { + _downstreamHost = downstreamHost; + return this; + } + + public ServiceProviderConfiguraionBuilder WithDownstreamPort(int downstreamPort) + { + _downstreamPort = downstreamPort; + return this; + } + + public ServiceProviderConfiguraionBuilder WithUseServiceDiscovery(bool userServiceDiscovery) + { + _userServiceDiscovery = userServiceDiscovery; + return this; + } + + public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProvider(string serviceDiscoveryProvider) + { + _serviceDiscoveryProvider = serviceDiscoveryProvider; + return this; + } + + public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProviderHost(string serviceDiscoveryProviderHost) + { + _serviceDiscoveryProviderHost = serviceDiscoveryProviderHost; + return this; + } + + public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProviderPort(int serviceDiscoveryProviderPort) + { + _serviceDiscoveryProviderPort = serviceDiscoveryProviderPort; + return this; + } + + + public ServiceProviderConfiguraion Build() + { + return new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _downstreamPort, _userServiceDiscovery, + _serviceDiscoveryProvider, _serviceDiscoveryProviderHost,_serviceDiscoveryProviderPort); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index f550edac..380e193f 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -4,13 +4,14 @@ using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Ocelot.Configuration.Builder; 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 { @@ -24,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 _logger; private readonly ILoadBalancerFactory _loadBalanceFactory; private readonly ILoadBalancerHouse _loadBalancerHouse; + private readonly IQoSProviderFactory _qoSProviderFactory; + private readonly IQosProviderHouse _qosProviderHouse; public FileOcelotConfigurationCreator( IOptions options, @@ -36,10 +40,14 @@ namespace Ocelot.Configuration.Creator IClaimToThingConfigurationParser claimToThingConfigurationParser, ILogger 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; @@ -53,10 +61,6 @@ namespace Ocelot.Configuration.Creator return new OkResponse(config); } - /// - /// This method is meant to be tempoary to convert a config to an ocelot config...probably wont keep this but we will see - /// will need a refactor at some point as its crap - /// private async Task SetUpConfiguration() { var response = _configurationValidator.IsValid(_options.Value); @@ -86,82 +90,154 @@ namespace Ocelot.Configuration.Creator private async Task SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) { - var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey); + var isAuthenticated = IsAuthenticated(fileReRoute); - var upstreamTemplate = BuildUpstreamTemplate(fileReRoute); + var isAuthorised = IsAuthorised(fileReRoute); - var isAuthenticated = !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); + var isCached = IsCached(fileReRoute); - var isAuthorised = fileReRoute.RouteClaimsRequirement?.Count > 0; + var requestIdKey = BuildRequestId(fileReRoute, globalConfiguration); - var isCached = fileReRoute.FileCacheOptions.TtlSeconds > 0; + var reRouteKey = BuildReRouteKey(fileReRoute); - var isQos = fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions.TimeoutValue >0; + var upstreamTemplatePattern = BuildUpstreamTemplatePattern(fileReRoute); - var requestIdKey = globalRequestIdConfiguration - ? globalConfiguration.RequestIdKey - : fileReRoute.RequestIdKey; + var isQos = IsQoS(fileReRoute); - var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName) - && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); + var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration); - //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.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}"; + var authOptionsForRoute = BuildAuthenticationOptions(fileReRoute); - ReRoute reRoute; + var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest); + var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest); - var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; + var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest); - var serviceProviderConfiguration = new ServiceProviderConfiguraion(fileReRoute.ServiceName, - fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, useServiceDiscovery, - globalConfiguration?.ServiceDiscoveryProvider?.Provider, globalConfiguration?.ServiceDiscoveryProvider?.Host, - serviceProviderPort); + var qosOptions = BuildQoSOptions(fileReRoute); - if (isAuthenticated) - { - var authOptionsForRoute = new AuthenticationOptions(fileReRoute.AuthenticationOptions.Provider, - fileReRoute.AuthenticationOptions.ProviderRootUrl, fileReRoute.AuthenticationOptions.ScopeName, - fileReRoute.AuthenticationOptions.RequireHttps, fileReRoute.AuthenticationOptions.AdditionalScopes, - fileReRoute.AuthenticationOptions.ScopeSecret); + var reRoute = new ReRouteBuilder() + .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) + .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) + .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) + .WithUpstreamTemplatePattern(upstreamTemplatePattern) + .WithIsAuthenticated(isAuthenticated) + .WithAuthenticationOptions(authOptionsForRoute) + .WithClaimsToHeaders(claimsToHeaders) + .WithClaimsToClaims(claimsToClaims) + .WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement) + .WithIsAuthorised(isAuthorised) + .WithClaimsToQueries(claimsToQueries) + .WithRequestIdKey(requestIdKey) + .WithIsCached(isCached) + .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)) + .WithDownstreamScheme(fileReRoute.DownstreamScheme) + .WithLoadBalancer(fileReRoute.LoadBalancer) + .WithDownstreamHost(fileReRoute.DownstreamHost) + .WithDownstreamPort(fileReRoute.DownstreamPort) + .WithLoadBalancerKey(reRouteKey) + .WithServiceProviderConfiguraion(serviceProviderConfiguration) + .WithIsQos(isQos) + .WithQosOptions(qosOptions) + .Build(); - var claimsToHeaders = GetAddThingsToRequest(fileReRoute.AddHeadersToRequest); - var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest); - var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest); - - reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), - fileReRoute.UpstreamTemplate, - fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, - authOptionsForRoute, claimsToHeaders, claimsToClaims, - fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, - requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds) - , fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, - serviceProviderConfiguration, isQos, - new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue)); - } - else - { - reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), - fileReRoute.UpstreamTemplate, - fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, - null, new List(), new List(), - fileReRoute.RouteClaimsRequirement, isAuthorised, new List(), - requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), - fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, - serviceProviderConfiguration, isQos, - new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue)); - } - - var loadBalancer = await _loadBalanceFactory.Get(reRoute); - _loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer); + await SetupLoadBalancer(reRoute); + SetupQosProvider(reRoute); return reRoute; } - private string BuildUpstreamTemplate(FileReRoute reRoute) + private QoSOptions BuildQoSOptions(FileReRoute fileReRoute) { - var upstreamTemplate = reRoute.UpstreamTemplate; + 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); + } + + private bool IsAuthorised(FileReRoute fileReRoute) + { + return fileReRoute.RouteClaimsRequirement?.Count > 0; + } + + private bool IsCached(FileReRoute fileReRoute) + { + return fileReRoute.FileCacheOptions.TtlSeconds > 0; + } + + private string BuildRequestId(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) + { + var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey); + + var requestIdKey = globalRequestIdConfiguration + ? globalConfiguration.RequestIdKey + : fileReRoute.RequestIdKey; + + return requestIdKey; + } + + 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}"; + return loadBalancerKey; + } + + private AuthenticationOptions BuildAuthenticationOptions(FileReRoute fileReRoute) + { + return new AuthenticationOptionsBuilder() + .WithProvider(fileReRoute.AuthenticationOptions?.Provider) + .WithProviderRootUrl(fileReRoute.AuthenticationOptions?.ProviderRootUrl) + .WithScopeName(fileReRoute.AuthenticationOptions?.ScopeName) + .WithRequireHttps(fileReRoute.AuthenticationOptions.RequireHttps) + .WithAdditionalScopes(fileReRoute.AuthenticationOptions?.AdditionalScopes) + .WithScopeSecret(fileReRoute.AuthenticationOptions?.ScopeSecret) + .Build(); + } + + private async Task SetupLoadBalancer(ReRoute reRoute) + { + var loadBalancer = await _loadBalanceFactory.Get(reRoute); + _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) + { + var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName) + && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); + + var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; + + return new ServiceProviderConfiguraionBuilder() + .WithServiceName(fileReRoute.ServiceName) + .WithDownstreamHost(fileReRoute.DownstreamHost) + .WithDownstreamPort(fileReRoute.DownstreamPort) + .WithUseServiceDiscovery(useServiceDiscovery) + .WithServiceDiscoveryProvider(globalConfiguration?.ServiceDiscoveryProvider?.Provider) + .WithServiceDiscoveryProviderHost(globalConfiguration?.ServiceDiscoveryProvider?.Host) + .WithServiceDiscoveryProviderPort(serviceProviderPort) + .Build(); + } + + private string BuildUpstreamTemplatePattern(FileReRoute reRoute) + { + var upstreamTemplate = reRoute.UpstreamPathTemplate; upstreamTemplate = upstreamTemplate.SetLastCharacterAs('/'); @@ -183,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}"; @@ -190,7 +271,7 @@ namespace Ocelot.Configuration.Creator return route; } - private List GetAddThingsToRequest(Dictionary thingBeingAdded) + private List BuildAddThingsToRequest(Dictionary thingBeingAdded) { var claimsToTHings = new List(); diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 9ab898ea..bdb8f87f 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -16,7 +16,7 @@ namespace Ocelot.Configuration.File } public string DownstreamPathTemplate { get; set; } - public string UpstreamTemplate { get; set; } + public string UpstreamPathTemplate { get; set; } public string UpstreamHttpMethod { get; set; } public FileAuthenticationOptions AuthenticationOptions { get; set; } public Dictionary AddHeadersToRequest { get; set; } diff --git a/src/Ocelot/Configuration/QoSOptions.cs b/src/Ocelot/Configuration/QoSOptions.cs index 8584c57e..29145888 100644 --- a/src/Ocelot/Configuration/QoSOptions.cs +++ b/src/Ocelot/Configuration/QoSOptions.cs @@ -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); diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index c38d55d5..ab9405d0 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -1,31 +1,42 @@ using System; using System.Collections.Generic; +using System.Net.Http; using Ocelot.Values; namespace Ocelot.Configuration { public class ReRoute { - public ReRoute(DownstreamPathTemplate downstreamPathTemplate, - string upstreamTemplate, string upstreamHttpMethod, + public ReRoute(PathTemplate downstreamPathTemplate, + PathTemplate upstreamTemplate, + HttpMethod upstreamHttpMethod, string upstreamTemplatePattern, - bool isAuthenticated, AuthenticationOptions authenticationOptions, + bool isAuthenticated, + AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, List claimsToClaims, - Dictionary routeClaimsRequirement, bool isAuthorised, + Dictionary routeClaimsRequirement, + bool isAuthorised, List claimsToQueries, - string requestIdKey, bool isCached, CacheOptions fileCacheOptions, - string downstreamScheme, string loadBalancer, string downstreamHost, - int downstreamPort, string loadBalancerKey, ServiceProviderConfiguraion serviceProviderConfiguraion, - bool isQos,QoSOptions qos) + string requestIdKey, + bool isCached, + CacheOptions fileCacheOptions, + string downstreamScheme, + string loadBalancer, + string downstreamHost, + int downstreamPort, + string reRouteKey, + ServiceProviderConfiguraion serviceProviderConfiguraion, + bool isQos, + QoSOptions qos) { - LoadBalancerKey = loadBalancerKey; + ReRouteKey = reRouteKey; ServiceProviderConfiguraion = serviceProviderConfiguraion; LoadBalancer = loadBalancer; DownstreamHost = downstreamHost; DownstreamPort = downstreamPort; DownstreamPathTemplate = downstreamPathTemplate; - UpstreamTemplate = upstreamTemplate; + UpstreamPathTemplate = upstreamTemplate; UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; IsAuthenticated = isAuthenticated; @@ -46,11 +57,11 @@ namespace Ocelot.Configuration QosOptions = qos; } - public string LoadBalancerKey {get;private set;} - public DownstreamPathTemplate DownstreamPathTemplate { get; private set; } - public string UpstreamTemplate { 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; } - public string UpstreamHttpMethod { get; private set; } + public HttpMethod UpstreamHttpMethod { get; private set; } public bool IsAuthenticated { get; private set; } public bool IsAuthorised { get; private set; } public AuthenticationOptions AuthenticationOptions { get; private set; } diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs index 412613eb..98301ab4 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs @@ -54,7 +54,7 @@ namespace Ocelot.Configuration.Validator continue; } - var error = new UnsupportedAuthenticationProviderError($"{reRoute.AuthenticationOptions?.Provider} is unsupported authentication provider, upstream template is {reRoute.UpstreamTemplate}, upstream method is {reRoute.UpstreamHttpMethod}"); + var error = new UnsupportedAuthenticationProviderError($"{reRoute.AuthenticationOptions?.Provider} is unsupported authentication provider, upstream template is {reRoute.UpstreamPathTemplate}, upstream method is {reRoute.UpstreamHttpMethod}"); errors.Add(error); } @@ -94,18 +94,18 @@ namespace Ocelot.Configuration.Validator private ConfigurationValidationResult CheckForDupliateReRoutes(FileConfiguration configuration) { var hasDupes = configuration.ReRoutes - .GroupBy(x => new { x.UpstreamTemplate, x.UpstreamHttpMethod }).Any(x => x.Skip(1).Any()); + .GroupBy(x => new { x.UpstreamPathTemplate, x.UpstreamHttpMethod }).Any(x => x.Skip(1).Any()); if (!hasDupes) { return new ConfigurationValidationResult(false); } - var dupes = configuration.ReRoutes.GroupBy(x => new { x.UpstreamTemplate, x.UpstreamHttpMethod }) + var dupes = configuration.ReRoutes.GroupBy(x => new { x.UpstreamPathTemplate, x.UpstreamHttpMethod }) .Where(x => x.Skip(1).Any()); var errors = dupes - .Select(d => new DownstreamPathTemplateAlreadyUsedError(string.Format("Duplicate DownstreamPath: {0}", d.Key.UpstreamTemplate))) + .Select(d => new DownstreamPathTemplateAlreadyUsedError(string.Format("Duplicate DownstreamPath: {0}", d.Key.UpstreamPathTemplate))) .Cast() .ToList(); diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 0a6bd42c..e9be9841 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -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(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index eacd6912..8b6447c7 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -26,15 +26,22 @@ namespace Ocelot.DownstreamRouteFinder.Finder { var configuration = await _configProvider.Get(); - var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod, upstreamHttpMethod, StringComparison.CurrentCultureIgnoreCase)); + var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod.Method, upstreamHttpMethod, StringComparison.CurrentCultureIgnoreCase)); foreach (var reRoute in applicableReRoutes) { + if (upstreamUrlPath == reRoute.UpstreamTemplatePattern) + { + var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamPathTemplate.Value); + + return new OkResponse(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute)); + } + var urlMatch = _urlMatcher.Match(upstreamUrlPath, reRoute.UpstreamTemplatePattern); if (urlMatch.Data.Match) { - var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamTemplate); + var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamPathTemplate.Value); return new OkResponse(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute)); } diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs index 9e925631..3c42b4f4 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs @@ -8,7 +8,7 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer { public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer { - public Response Replace(DownstreamPathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues) + public Response Replace(PathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues) { var downstreamPath = new StringBuilder(); diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs index 72d5d4b6..647af63a 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs @@ -7,6 +7,6 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer { public interface IDownstreamPathPlaceholderReplacer { - Response Replace(DownstreamPathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues); + Response Replace(PathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues); } } \ No newline at end of file diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index d24988b9..373ac441 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -25,6 +25,8 @@ ServicesAreNullError, ServicesAreEmptyError, UnableToFindServiceDiscoveryProviderError, - UnableToFindLoadBalancerError + UnableToFindLoadBalancerError, + RequestTimedOutError, + UnableToFindQoSProviderError } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs index 08e45d2b..72ccdba1 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs @@ -13,17 +13,8 @@ namespace Ocelot.LoadBalancer.LoadBalancers } public async Task Get(ReRoute reRoute) - { - var serviceConfig = new ServiceProviderConfiguraion( - reRoute.ServiceProviderConfiguraion.ServiceName, - reRoute.ServiceProviderConfiguraion.DownstreamHost, - reRoute.ServiceProviderConfiguraion.DownstreamPort, - reRoute.ServiceProviderConfiguraion.UseServiceDiscovery, - reRoute.ServiceProviderConfiguraion.ServiceDiscoveryProvider, - reRoute.ServiceProviderConfiguraion.ServiceProviderHost, - reRoute.ServiceProviderConfiguraion.ServiceProviderPort); - - var serviceProvider = _serviceProviderFactory.Get(serviceConfig); + { + var serviceProvider = _serviceProviderFactory.Get(reRoute.ServiceProviderConfiguraion); switch (reRoute.LoadBalancer) { diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index ce37f828..8e26cbf2 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -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); diff --git a/src/Ocelot/Request/Builder/HttpRequestCreator.cs b/src/Ocelot/Request/Builder/HttpRequestCreator.cs index f326933c..3264db59 100644 --- a/src/Ocelot/Request/Builder/HttpRequestCreator.cs +++ b/src/Ocelot/Request/Builder/HttpRequestCreator.cs @@ -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); diff --git a/src/Ocelot/Request/Builder/IRequestCreator.cs b/src/Ocelot/Request/Builder/IRequestCreator.cs index 3417d933..7db999b1 100644 --- a/src/Ocelot/Request/Builder/IRequestCreator.cs +++ b/src/Ocelot/Request/Builder/IRequestCreator.cs @@ -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); } } diff --git a/src/Ocelot/Request/Builder/RequestBuilder.cs b/src/Ocelot/Request/Builder/RequestBuilder.cs index e0201bc8..ea6511ce 100644 --- a/src/Ocelot/Request/Builder/RequestBuilder.cs +++ b/src/Ocelot/Request/Builder/RequestBuilder.cs @@ -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() diff --git a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs index aafa2f3d..991e84da 100644 --- a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs +++ b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs @@ -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(); } @@ -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"); diff --git a/src/Ocelot/Request/Request.cs b/src/Ocelot/Request/Request.cs index a1e62834..680ab757 100644 --- a/src/Ocelot/Request/Request.cs +++ b/src/Ocelot/Request/Request.cs @@ -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; } } } diff --git a/src/Ocelot/Requester/CircuitBreakingDelegatingHandler.cs b/src/Ocelot/Requester/CircuitBreakingDelegatingHandler.cs deleted file mode 100644 index 4adc0eeb..00000000 --- a/src/Ocelot/Requester/CircuitBreakingDelegatingHandler.cs +++ /dev/null @@ -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() - .Or() - .Or() - .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 SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - Task responseTask = null; - - try - { - responseTask = Policy.WrapAsync(_circuitBreakerPolicy, _timeoutPolicy).ExecuteAsync(() => - { - 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; - } - } -} diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index c3c98ac0..da37ee02 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -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> handlers = new Dictionary>(); + private readonly Dictionary> _handlers = new Dictionary>(); - 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; diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index 77ab90cc..5a2c86c7 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -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(); @@ -19,14 +19,15 @@ namespace Ocelot.Requester public async Task> 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(response); } - catch (Exception exception) + catch (TimeoutRejectedException exception) { return - new ErrorResponse(new List - { - new UnableToCompleteRequestError(exception) - }); + new ErrorResponse(new RequestTimedOutError(exception)); + } + catch (BrokenCircuitException exception) + { + return + new ErrorResponse(new RequestTimedOutError(exception)); + } + catch (Exception exception) + { + return new ErrorResponse(new UnableToCompleteRequestError(exception)); } } } diff --git a/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs b/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs new file mode 100644 index 00000000..a50ec91b --- /dev/null +++ b/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs @@ -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 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; + } + } + } +} diff --git a/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs b/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs new file mode 100644 index 00000000..d1f63ea1 --- /dev/null +++ b/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs @@ -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(); + + _timeoutPolicy = Policy.TimeoutAsync(reRoute.QosOptions.TimeoutValue, reRoute.QosOptions.TimeoutStrategy); + + _circuitBreakerPolicy = Policy + .Handle() + .Or() + .Or() + .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 Get(string key); + Response Add(string key, IQoSProvider loadBalancer); + } + + public class QosProviderHouse : IQosProviderHouse + { + private readonly Dictionary _qoSProviders; + + public QosProviderHouse() + { + _qoSProviders = new Dictionary(); + } + + public Response Get(string key) + { + IQoSProvider qoSProvider; + + if (_qoSProviders.TryGetValue(key, out qoSProvider)) + { + return new OkResponse(_qoSProviders[key]); + } + + return new ErrorResponse(new List() + { + 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(); + } + } +} diff --git a/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs b/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs new file mode 100644 index 00000000..46ec65f3 --- /dev/null +++ b/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs @@ -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) + { + } + } +} diff --git a/src/Ocelot/Requester/RequestTimedOutError.cs b/src/Ocelot/Requester/RequestTimedOutError.cs new file mode 100644 index 00000000..38afce1a --- /dev/null +++ b/src/Ocelot/Requester/RequestTimedOutError.cs @@ -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) + { + } + } +} diff --git a/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs b/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs index 6ecc20eb..45aeafd1 100644 --- a/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs +++ b/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs @@ -22,6 +22,11 @@ namespace Ocelot.Responder return new OkResponse(403); } + if (errors.Any(e => e.Code == OcelotErrorCode.RequestTimedOutError)) + { + return new OkResponse(503); + } + return new OkResponse(404); } } diff --git a/src/Ocelot/Responses/ErrorResponseGeneric.cs b/src/Ocelot/Responses/ErrorResponseGeneric.cs index 77230503..044d6f61 100644 --- a/src/Ocelot/Responses/ErrorResponseGeneric.cs +++ b/src/Ocelot/Responses/ErrorResponseGeneric.cs @@ -5,7 +5,13 @@ namespace Ocelot.Responses { public class ErrorResponse : Response { - public ErrorResponse(List errors) : base(errors) + public ErrorResponse(Error error) + : base(new List {error}) + { + + } + public ErrorResponse(List errors) + : base(errors) { } } diff --git a/src/Ocelot/Values/DownstreamPathTemplate.cs b/src/Ocelot/Values/DownstreamPathTemplate.cs index a4c720eb..b0b53418 100644 --- a/src/Ocelot/Values/DownstreamPathTemplate.cs +++ b/src/Ocelot/Values/DownstreamPathTemplate.cs @@ -1,8 +1,8 @@ namespace Ocelot.Values { - public class DownstreamPathTemplate + public class PathTemplate { - public DownstreamPathTemplate(string value) + public PathTemplate(string value) { Value = value; } diff --git a/test/.DS_Store b/test/.DS_Store deleted file mode 100644 index e73160dc..00000000 Binary files a/test/.DS_Store and /dev/null differ diff --git a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs index 98458f9b..6dca1dc2 100644 --- a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs @@ -47,7 +47,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = _downstreamServicePort, DownstreamHost = _downstreamServiceHost, DownstreamScheme = _downstreamServiceScheme, - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions { @@ -85,7 +85,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = _downstreamServicePort, DownstreamHost = _downstreamServiceHost, DownstreamScheme = _downstreamServiceScheme, - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions { @@ -123,7 +123,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = _downstreamServicePort, DownstreamHost = _downstreamServiceHost, DownstreamScheme = _downstreamServiceScheme, - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions { @@ -163,7 +163,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = _downstreamServicePort, DownstreamHost = _downstreamServiceHost, DownstreamScheme = _downstreamServiceScheme, - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions @@ -204,7 +204,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = _downstreamServicePort, DownstreamHost = _downstreamServiceHost, DownstreamScheme = _downstreamServiceScheme, - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions { diff --git a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs index 1f86c6ff..a89616b9 100644 --- a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs @@ -41,7 +41,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51876, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions { @@ -98,7 +98,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51876, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions { diff --git a/test/Ocelot.AcceptanceTests/CachingTests.cs b/test/Ocelot.AcceptanceTests/CachingTests.cs index e4e628af..a8364855 100644 --- a/test/Ocelot.AcceptanceTests/CachingTests.cs +++ b/test/Ocelot.AcceptanceTests/CachingTests.cs @@ -35,7 +35,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", FileCacheOptions = new FileCacheOptions { @@ -71,7 +71,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", FileCacheOptions = new FileCacheOptions { diff --git a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs index ae545add..85e8fd68 100644 --- a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs @@ -34,7 +34,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/products/{productId}", + UpstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } } @@ -61,7 +61,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/products/{productId}", + UpstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false, } @@ -89,7 +89,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/products/{productId}", + UpstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, } @@ -117,7 +117,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/PRODUCTS/{productId}", + UpstreamPathTemplate = "/PRODUCTS/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, } @@ -145,7 +145,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/products/{productId}", + UpstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, } @@ -173,7 +173,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/PRODUCTS/{productId}", + UpstreamPathTemplate = "/PRODUCTS/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, } diff --git a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs index 08bbd968..2d6bf163 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs @@ -55,7 +55,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 52876, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions { diff --git a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs index 04dc25db..0c10c3e4 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs @@ -55,7 +55,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 57876, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions { diff --git a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs index cee75cef..7d33febb 100644 --- a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs +++ b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs @@ -49,7 +49,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 41879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } } @@ -86,7 +86,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 41879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } @@ -124,7 +124,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 41879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } @@ -162,7 +162,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 41879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } } @@ -199,7 +199,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 41879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } } @@ -236,7 +236,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 41879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } } diff --git a/test/Ocelot.AcceptanceTests/QoSTests.cs b/test/Ocelot.AcceptanceTests/QoSTests.cs new file mode 100644 index 00000000..0eb3d8a4 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/QoSTests.cs @@ -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 + { + 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 + { + 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(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/RequestIdTests.cs b/test/Ocelot.AcceptanceTests/RequestIdTests.cs index 4ad5bd8c..46a5fc13 100644 --- a/test/Ocelot.AcceptanceTests/RequestIdTests.cs +++ b/test/Ocelot.AcceptanceTests/RequestIdTests.cs @@ -37,7 +37,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", RequestIdKey = _steps.RequestIdKey, } @@ -65,7 +65,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } @@ -95,8 +95,8 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", - UpstreamHttpMethod ="Get" + UpstreamPathTemplate = "/", + UpstreamHttpMethod = "Get", } }, GlobalConfiguration = new FileGlobalConfiguration diff --git a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs index 81307781..f9222b86 100644 --- a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs +++ b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs @@ -30,7 +30,7 @@ namespace Ocelot.AcceptanceTests new FileReRoute { DownstreamPathTemplate = "http://localhost:53876/", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get" } } diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 479c69ff..58e65bff 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -44,7 +44,7 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } @@ -73,7 +73,7 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } @@ -102,7 +102,7 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", DownstreamHost = "localhost/", DownstreamPort = 51879, - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } @@ -131,7 +131,7 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, - UpstreamTemplate = "/products/", + UpstreamPathTemplate = "/products/", UpstreamHttpMethod = "Get", } @@ -160,9 +160,8 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, - UpstreamTemplate = "/products", + UpstreamPathTemplate = "/products", UpstreamHttpMethod = "Get", - } } }; @@ -182,22 +181,23 @@ namespace Ocelot.AcceptanceTests var configuration = new FileConfiguration { ReRoutes = new List + { + new FileReRoute { - new FileReRoute + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, + UpstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = "Get", + QoSOptions = new FileQoSOptions() { - DownstreamPathTemplate = "/products", - DownstreamScheme = "http", - DownstreamHost = "localhost", - DownstreamPort = 51879, - UpstreamTemplate = "/products/{productId}", - UpstreamHttpMethod = "Get", - QoSOptions = new FileQoSOptions() - { ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000 } + DurationOfBreak = 5, + TimeoutValue = 5000 } } + } }; this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/products", 200, "Hello from Laura")) @@ -221,7 +221,7 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, - UpstreamTemplate = "/products/{productId}", + UpstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } } @@ -249,7 +249,7 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", DownstreamPort = 51879, DownstreamScheme = "http", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Post", } } @@ -274,7 +274,7 @@ namespace Ocelot.AcceptanceTests new FileReRoute { DownstreamPathTemplate = "/newThing", - UpstreamTemplate = "/newThing", + UpstreamPathTemplate = "/newThing", DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index ef7e0dd7..fcf75b14 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -68,7 +68,7 @@ namespace Ocelot.AcceptanceTests { DownstreamPathTemplate = "/", DownstreamScheme = "http", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", ServiceName = serviceName, LoadBalancer = "LeastConnection", diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json deleted file mode 100755 index b155bee6..00000000 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ /dev/null @@ -1 +0,0 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs index e76a2b28..8bf53607 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs @@ -6,6 +6,7 @@ using Moq; using Ocelot.Authentication.Handler; using Ocelot.Authentication.Handler.Creator; using Ocelot.Authentication.Handler.Factory; +using Ocelot.Configuration.Builder; using Ocelot.Errors; using Ocelot.Responses; using Shouldly; @@ -33,7 +34,11 @@ namespace Ocelot.UnitTests.Authentication [Fact] public void should_return_identity_server_access_token_handler() { - this.Given(x => x.GivenTheAuthenticationOptionsAre(new AuthenticationOptions("IdentityServer", "","",false, new List(), ""))) + var authenticationOptions = new AuthenticationOptionsBuilder() + .WithProvider("IdentityServer") + .Build(); + + this.Given(x => x.GivenTheAuthenticationOptionsAre(authenticationOptions)) .And(x => x.GivenTheCreatorReturns()) .When(x => x.WhenIGetFromTheFactory()) .Then(x => x.ThenTheHandlerIsReturned("IdentityServer")) @@ -43,7 +48,10 @@ namespace Ocelot.UnitTests.Authentication [Fact] public void should_return_error_if_cannot_create_handler() { - this.Given(x => x.GivenTheAuthenticationOptionsAre(new AuthenticationOptions("IdentityServer", "", "", false, new List(), ""))) + var authenticationOptions = new AuthenticationOptionsBuilder() + .Build(); + + this.Given(x => x.GivenTheAuthenticationOptionsAre(authenticationOptions)) .And(x => x.GivenTheCreatorReturnsAnError()) .When(x => x.WhenIGetFromTheFactory()) .Then(x => x.ThenAnErrorResponseIsReturned()) @@ -58,7 +66,7 @@ namespace Ocelot.UnitTests.Authentication private void GivenTheCreatorReturnsAnError() { _creator - .Setup(x => x.CreateIdentityServerAuthenticationHandler(It.IsAny(), It.IsAny())) + .Setup(x => x.Create(It.IsAny(), It.IsAny())) .Returns(new ErrorResponse(new List { new UnableToCreateAuthenticationHandlerError($"Unable to create authentication handler for xxx") @@ -68,7 +76,7 @@ namespace Ocelot.UnitTests.Authentication private void GivenTheCreatorReturns() { _creator - .Setup(x => x.CreateIdentityServerAuthenticationHandler(It.IsAny(), It.IsAny())) + .Setup(x => x.Create(It.IsAny(), It.IsAny())) .Returns(new OkResponse(x => Task.CompletedTask)); } diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs index ff467aa7..f60eb83c 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs @@ -71,7 +71,9 @@ namespace Ocelot.UnitTests.Authentication [Fact] public void should_call_next_middleware_if_route_is_not_authenticated() { - this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().Build()))) + this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder() + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheUserIsAuthenticated()) .BDDfy(); diff --git a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs index 8d681e03..35435fc2 100644 --- a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs @@ -63,7 +63,11 @@ namespace Ocelot.UnitTests.Authorization [Fact] public void should_call_authorisation_service() { - this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().WithIsAuthorised(true).Build()))) + this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithIsAuthorised(true) + .WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get") + .Build()))) .And(x => x.GivenTheAuthServiceReturns(new OkResponse(true))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheAuthServiceIsCalledCorrectly()) diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index 9190dbe9..66532a18 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -87,7 +87,12 @@ namespace Ocelot.UnitTests.Cache private void GivenTheDownstreamRouteIs() { - var reRoute = new ReRouteBuilder().WithIsCached(true).WithCacheOptions(new CacheOptions(100)).Build(); + var reRoute = new ReRouteBuilder() + .WithIsCached(true) + .WithCacheOptions(new CacheOptions(100)) + .WithUpstreamHttpMethod("Get") + .Build(); + var downstreamRoute = new DownstreamRoute(new List(), reRoute); _scopedRepo diff --git a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs index 8822e6b2..90a16b17 100644 --- a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs @@ -72,6 +72,7 @@ namespace Ocelot.UnitTests.Claims { new ClaimToThing("sub", "UserType", "|", 0) }) + .WithUpstreamHttpMethod("Get") .Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) diff --git a/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs b/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs index 8a3e24f9..382471c6 100644 --- a/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs @@ -29,7 +29,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamPathTemplate = "http://www.bbc.co.uk/api/products/{productId}", - UpstreamTemplate = "http://asdf.com" + UpstreamPathTemplate = "http://asdf.com" } } })) @@ -48,7 +48,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamPathTemplate = "/api/products/", - UpstreamTemplate = "http://asdf.com" + UpstreamPathTemplate = "http://asdf.com" } } })) @@ -67,7 +67,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamPathTemplate = "/api/products/", - UpstreamTemplate = "http://asdf.com", + UpstreamPathTemplate = "http://asdf.com", AuthenticationOptions = new FileAuthenticationOptions { Provider = "IdentityServer" @@ -90,7 +90,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamPathTemplate = "/api/products/", - UpstreamTemplate = "http://asdf.com", + UpstreamPathTemplate = "http://asdf.com", AuthenticationOptions = new FileAuthenticationOptions { Provider = "BootyBootyBottyRockinEverywhere" @@ -114,12 +114,12 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamPathTemplate = "/api/products/", - UpstreamTemplate = "http://asdf.com" + UpstreamPathTemplate = "http://asdf.com" }, new FileReRoute { DownstreamPathTemplate = "http://www.bbc.co.uk", - UpstreamTemplate = "http://asdf.com" + UpstreamPathTemplate = "http://asdf.com" } } })) diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index e1da7de0..fa1bbda3 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -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 _loadBalancerFactory; private readonly Mock _loadBalancerHouse; private readonly Mock _loadBalancer; + private readonly Mock _qosProviderFactory; + private readonly Mock _qosProviderHouse; + private readonly Mock _qosProvider; public FileConfigurationCreatorTests() { + _qosProviderFactory = new Mock(); + _qosProviderHouse = new Mock(); + _qosProvider = new Mock(); _logger = new Mock>(); _configParser = new Mock(); _validator = new Mock(); @@ -40,7 +47,8 @@ namespace Ocelot.UnitTests.Configuration _loadBalancer = new Mock(); _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( _fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object, - _loadBalancerFactory.Object, _loadBalancerHouse.Object); + _loadBalancerFactory.Object, _loadBalancerHouse.Object, + _qosProviderFactory.Object, _qosProviderHouse.Object); } [Fact] @@ -53,7 +61,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamHost = "127.0.0.1", - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } @@ -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 + { + 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() { @@ -78,7 +115,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamHost = "127.0.0.1", - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } @@ -91,7 +128,7 @@ namespace Ocelot.UnitTests.Configuration new ReRouteBuilder() .WithDownstreamHost("127.0.0.1") .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .Build() @@ -109,7 +146,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamScheme = "https", - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } @@ -122,7 +159,7 @@ namespace Ocelot.UnitTests.Configuration new ReRouteBuilder() .WithDownstreamScheme("https") .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .Build() @@ -139,7 +176,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false, @@ -161,13 +198,15 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") - .WithServiceName("ProductService") - .WithUseServiceDiscovery(true) - .WithServiceDiscoveryProvider("consul") - .WithServiceDiscoveryAddress("127.0.01") + .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder() + .WithUseServiceDiscovery(true) + .WithServiceDiscoveryProvider("consul") + .WithServiceDiscoveryProviderHost("127.0.0.1") + .WithServiceName("ProductService") + .Build()) .Build() })) .BDDfy(); @@ -182,7 +221,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false, @@ -195,12 +234,12 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") - .WithUseServiceDiscovery(false) - .WithServiceDiscoveryProvider(null) - .WithServiceDiscoveryAddress(null) + .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder() + .WithUseServiceDiscovery(false) + .Build()) .Build() })) .BDDfy(); @@ -215,7 +254,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false @@ -228,7 +267,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .Build() @@ -245,7 +284,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get" } @@ -257,7 +296,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .Build() @@ -274,7 +313,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -287,7 +326,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") .Build() @@ -304,7 +343,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -321,7 +360,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") .WithRequestIdKey("blahhhh") @@ -339,7 +378,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -352,7 +391,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") .Build() @@ -363,18 +402,23 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void should_create_with_headers_to_extract() { + var authenticationOptions = new AuthenticationOptionsBuilder() + .WithProvider("IdentityServer") + .WithProviderRootUrl("http://localhost:51888") + .WithRequireHttps(false) + .WithScopeSecret("secret") + .WithScopeName("api") + .WithAdditionalScopes(new List()) + .Build(); + var expected = new List { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") - .WithAuthenticationProvider("IdentityServer") - .WithAuthenticationProviderUrl("http://localhost:51888") - .WithRequireHttps(false) - .WithScopeSecret("secret") - .WithAuthenticationProviderScopeName("api") + .WithAuthenticationOptions(authenticationOptions) .WithClaimsToHeaders(new List { new ClaimToThing("CustomerId", "CustomerId", "", 0), @@ -388,7 +432,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, @@ -427,18 +471,23 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void should_create_with_authentication_properties() { + var authenticationOptions = new AuthenticationOptionsBuilder() + .WithProvider("IdentityServer") + .WithProviderRootUrl("http://localhost:51888") + .WithRequireHttps(false) + .WithScopeSecret("secret") + .WithScopeName("api") + .WithAdditionalScopes(new List()) + .Build(); + var expected = new List { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") - .WithAuthenticationProvider("IdentityServer") - .WithAuthenticationProviderUrl("http://localhost:51888") - .WithRequireHttps(false) - .WithScopeSecret("secret") - .WithAuthenticationProviderScopeName("api") + .WithAuthenticationOptions(authenticationOptions) .Build() }; @@ -448,7 +497,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, @@ -481,7 +530,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}/variants/{variantId}", + UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -494,7 +543,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}/variants/{variantId}") + .WithUpstreamPathTemplate("/api/products/{productId}/variants/{variantId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$") .Build() @@ -511,7 +560,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}/variants/{variantId}/", + UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}/", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -524,7 +573,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}/variants/{variantId}/") + .WithUpstreamPathTemplate("/api/products/{productId}/variants/{variantId}/") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$") .Build() @@ -541,7 +590,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", DownstreamPathTemplate = "/api/products/", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -554,9 +603,9 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/api/products/") - .WithUpstreamTemplate("/") + .WithUpstreamPathTemplate("/") .WithUpstreamHttpMethod("Get") - .WithUpstreamTemplatePattern("/$") + .WithUpstreamTemplatePattern("^/$") .Build() })) .BDDfy(); @@ -591,7 +640,7 @@ namespace Ocelot.UnitTests.Configuration result.DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamPathTemplate.Value); result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod); - result.UpstreamTemplate.ShouldBe(expected.UpstreamTemplate); + result.UpstreamPathTemplate.Value.ShouldBe(expected.UpstreamPathTemplate.Value); result.UpstreamTemplatePattern.ShouldBe(expected.UpstreamTemplatePattern); } } @@ -631,5 +680,24 @@ namespace Ocelot.UnitTests.Configuration _loadBalancerHouse .Verify(x => x.Add(It.IsAny(), _loadBalancer.Object), Times.Once); } + + private void GivenTheQosProviderFactoryReturns() + { + _qosProviderFactory + .Setup(x => x.Get(It.IsAny())) + .Returns(_qosProvider.Object); + } + + private void TheQosProviderFactoryIsCalledCorrectly() + { + _qosProviderFactory + .Verify(x => x.Get(It.IsAny()), Times.Once); + } + + private void ThenTheQosProviderHouseIsCalledCorrectly() + { + _qosProviderHouse + .Verify(x => x.Add(It.IsAny(), _qosProvider.Object), Times.Once); + } } } diff --git a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs index ec46f914..9f437204 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -84,7 +84,10 @@ namespace Ocelot.UnitTests.Configuration public List ReRoutes => new List { - new ReRouteBuilder().WithDownstreamPathTemplate(_downstreamTemplatePath).Build() + new ReRouteBuilder() + .WithDownstreamPathTemplate(_downstreamTemplatePath) + .WithUpstreamHttpMethod("Get") + .Build() }; } } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index a80a3168..e7718f37 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -61,7 +61,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder [Fact] public void should_call_scoped_data_repository_correctly() { - this.Given(x => x.GivenTheDownStreamRouteFinderReturns(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("any old string").Build()))) + this.Given(x => x.GivenTheDownStreamRouteFinderReturns( + new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .BDDfy(); diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index dc9978b3..2fb8f28f 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -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>( + new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod("Get") + .WithUpstreamTemplatePattern("someUpstreamPath") + .Build() + })) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRoute( + new List(), + 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( @@ -45,7 +76,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder { new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamTemplate("someUpstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("someUpstreamPath") .Build() @@ -58,9 +89,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod("Get") .Build() ))) - .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) + .And(x => x.ThenTheUrlMatcherIsNotCalled()) .BDDfy(); } @@ -76,13 +108,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder { new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamTemplate("someUpstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("") .Build(), new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPathForAPost") - .WithUpstreamTemplate("someUpstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod("Post") .WithUpstreamTemplatePattern("") .Build() @@ -95,6 +127,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPathForAPost") + .WithUpstreamHttpMethod("Post") .Build() ))) .BDDfy(); @@ -103,12 +136,12 @@ 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 { new ReRouteBuilder() .WithDownstreamPathTemplate("somPath") - .WithUpstreamTemplate("somePath") + .WithUpstreamPathTemplate("somePath") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("somePath") .Build(), @@ -143,7 +176,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void ThenTheUrlMatcherIsCalledCorrectly() { _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamTemplate), Times.Once); + .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once); + } + + private void ThenTheUrlMatcherIsNotCalled() + { + _mockMatcher + .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Never); } private void GivenTheUrlMatcherReturns(Response match) diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs index ea069593..70dd747d 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs @@ -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() { diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index a01677b2..438710f6 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -72,7 +72,13 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator { var hostAndPort = new HostAndPort("127.0.0.1", 80); - this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("any old string").Build()))) + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithUpstreamHttpMethod("Get") + .Build()))) .And(x => x.GivenTheHostAndPortIs(hostAndPort)) .And(x => x.TheUrlReplacerReturns("/api/products/1")) .And(x => x.TheUrlBuilderReturns("http://127.0.0.1:80/api/products/1")) @@ -101,7 +107,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator { _downstreamPath = new OkResponse(new DownstreamPath(downstreamUrl)); _downstreamUrlTemplateVariableReplacer - .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) + .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) .Returns(_downstreamPath); } diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs index a7a5a89b..00445ee6 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs @@ -25,7 +25,12 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_no_template_variables() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().Build()))) + this.Given(x => x.GivenThereIsAUrlMatch( + new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("")) .BDDfy(); @@ -34,7 +39,13 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_no_template_variables_with_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch( + new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("/") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("/")) .BDDfy(); @@ -43,7 +54,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_no_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("api").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("api") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api")) .BDDfy(); @@ -52,7 +67,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_one_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("api/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("api/") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/")) .BDDfy(); @@ -61,7 +80,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_multiple_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("api/product/products/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("api/product/products/") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/product/products/")) .BDDfy(); @@ -75,7 +98,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new UrlPathPlaceholderNameAndValue("{productId}", "1") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, + new ReRouteBuilder() + .WithDownstreamPathTemplate("productservice/products/{productId}/") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/")) .BDDfy(); @@ -89,7 +116,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new UrlPathPlaceholderNameAndValue("{productId}", "1") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/variants").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, + new ReRouteBuilder() + .WithDownstreamPathTemplate("productservice/products/{productId}/variants") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants")) .BDDfy(); @@ -104,7 +135,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new UrlPathPlaceholderNameAndValue("{variantId}", "12") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/variants/{variantId}").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, + new ReRouteBuilder() + .WithDownstreamPathTemplate("productservice/products/{productId}/variants/{variantId}") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants/12")) .BDDfy(); @@ -120,7 +155,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new UrlPathPlaceholderNameAndValue("{categoryId}", "34") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/category/{categoryId}/products/{productId}/variants/{variantId}").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, + new ReRouteBuilder() + .WithDownstreamPathTemplate("productservice/category/{categoryId}/products/{productId}/variants/{variantId}") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/category/34/products/1/variants/12")) .BDDfy(); diff --git a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs index 3516d26b..032a76e9 100644 --- a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs @@ -72,6 +72,7 @@ namespace Ocelot.UnitTests.Headers { new ClaimToThing("UserId", "Subject", "", 0) }) + .WithUpstreamHttpMethod("Get") .Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index d030eb99..767b4272 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -24,18 +24,13 @@ namespace Ocelot.UnitTests.LoadBalancer _factory = new LoadBalancerFactory(_serviceProviderFactory.Object); } - private void GivenTheServiceProviderFactoryReturns() - { - _serviceProviderFactory - .Setup(x => x.Get(It.IsAny())) - .Returns(_serviceProvider.Object); - } - [Fact] public void should_return_no_load_balancer() { var reRoute = new ReRouteBuilder() - .Build(); + .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) + .WithUpstreamHttpMethod("Get") + .Build(); this.Given(x => x.GivenAReRoute(reRoute)) .And(x => x.GivenTheServiceProviderFactoryReturns()) @@ -49,6 +44,8 @@ namespace Ocelot.UnitTests.LoadBalancer { var reRoute = new ReRouteBuilder() .WithLoadBalancer("RoundRobin") + .WithUpstreamHttpMethod("Get") + .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) .Build(); this.Given(x => x.GivenAReRoute(reRoute)) @@ -63,6 +60,8 @@ namespace Ocelot.UnitTests.LoadBalancer { var reRoute = new ReRouteBuilder() .WithLoadBalancer("LeastConnection") + .WithUpstreamHttpMethod("Get") + .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) .Build(); this.Given(x => x.GivenAReRoute(reRoute)) @@ -77,6 +76,8 @@ namespace Ocelot.UnitTests.LoadBalancer { var reRoute = new ReRouteBuilder() .WithLoadBalancer("RoundRobin") + .WithUpstreamHttpMethod("Get") + .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) .Build(); this.Given(x => x.GivenAReRoute(reRoute)) @@ -86,6 +87,13 @@ namespace Ocelot.UnitTests.LoadBalancer .BDDfy(); } + private void GivenTheServiceProviderFactoryReturns() + { + _serviceProviderFactory + .Setup(x => x.Get(It.IsAny())) + .Returns(_serviceProvider.Object); + } + private void ThenTheServiceProviderIsCalledCorrectly() { _serviceProviderFactory diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index bd7667bf..e76f3c2f 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -68,6 +68,7 @@ namespace Ocelot.UnitTests.LoadBalancer { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() + .WithUpstreamHttpMethod("Get") .Build()); this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) @@ -84,6 +85,7 @@ namespace Ocelot.UnitTests.LoadBalancer { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() + .WithUpstreamHttpMethod("Get") .Build()); this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) @@ -99,6 +101,7 @@ namespace Ocelot.UnitTests.LoadBalancer { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() + .WithUpstreamHttpMethod("Get") .Build()); this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) diff --git a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs index 39b32937..f381ff1b 100644 --- a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs @@ -70,6 +70,7 @@ namespace Ocelot.UnitTests.QueryStrings { new ClaimToThing("UserId", "Subject", "", 0) }) + .WithUpstreamHttpMethod("Get") .Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) diff --git a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs index 6d4837d8..b674b015 100644 --- a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs @@ -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 _requestBuilder; private readonly Mock _scopedRepository; + private readonly Mock _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(); _requestBuilder = new Mock(); _scopedRepository = new Mock(); var builder = new WebHostBuilder() @@ -45,6 +48,7 @@ namespace Ocelot.UnitTests.Request { x.AddSingleton(); x.AddLogging(); + x.AddSingleton(_qosProviderHouse.Object); x.AddSingleton(_requestBuilder.Object); x.AddSingleton(_scopedRepository.Object); }) @@ -68,17 +72,26 @@ namespace Ocelot.UnitTests.Request var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithRequestIdKey("LSRequestId").Build()); - + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod("Get") + .Build()); this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) + .And(x => x.GivenTheQosProviderHouseReturns(new OkResponse(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 qosProvider) + { + _qosProviderHouse + .Setup(x => x.Get(It.IsAny())) + .Returns(qosProvider); + } + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { _downstreamRoute = new OkResponse(downstreamRoute); @@ -92,7 +105,7 @@ namespace Ocelot.UnitTests.Request _request = new OkResponse(request); _requestBuilder .Setup(x => x.Build(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(),It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(),It.IsAny(), It.IsAny())) .ReturnsAsync(_request); } diff --git a/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs b/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs index c317c8d7..9d1c2ed5 100644 --- a/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs +++ b/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs @@ -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 _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; } diff --git a/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs b/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs index 543613a8..26450ab8 100644 --- a/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs @@ -72,7 +72,9 @@ namespace Ocelot.UnitTests.RequestId var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("any old string") - .WithRequestIdKey("LSRequestId").Build()); + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod("Get") + .Build()); var requestId = Guid.NewGuid().ToString(); @@ -89,7 +91,9 @@ namespace Ocelot.UnitTests.RequestId var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("any old string") - .WithRequestIdKey("LSRequestId").Build()); + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod("Get") + .Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) .When(x => x.WhenICallTheMiddleware()) diff --git a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs index d2d62923..37815a64 100644 --- a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs @@ -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()) diff --git a/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs b/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs new file mode 100644 index 00000000..f4beb0f1 --- /dev/null +++ b/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs @@ -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 _loggerFactory; + private Mock _logger; + + public QoSProviderFactoryTests() + { + _logger = new Mock(); + _loggerFactory = new Mock(); + _loggerFactory + .Setup(x => x.CreateLogger()) + .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()) + .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()) + .BDDfy(); + } + + private void GivenAReRoute(ReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGetTheQoSProvider() + { + _result = _factory.Get(_reRoute); + } + + private void ThenTheQoSProviderIsReturned() + { + _result.ShouldBeOfType(); + } + } +} diff --git a/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs b/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs new file mode 100644 index 00000000..1e76a027 --- /dev/null +++ b/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs @@ -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 _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()) + .When(x => x.WhenWeGetTheQoSProvider(keyTwo)) + .Then(x => x.ThenTheQoSProviderIs()) + .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(); + } + + private void ThenTheQoSProviderIs() + { + _getResult.Data.ShouldBeOfType(); + } + + private void ThenItIsAdded() + { + _addResult.IsError.ShouldBe(false); + _addResult.ShouldBeOfType(); + } + + 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; } + } + } +} diff --git a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs index 78f78235..d2ac91e0 100644 --- a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs +++ b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs @@ -1,9 +1,8 @@ -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; using Ocelot.Errors; using Ocelot.Middleware; +using Ocelot.Requester; using Ocelot.Responder; using Ocelot.Responses; using Shouldly; @@ -23,6 +22,18 @@ namespace Ocelot.UnitTests.Responder _codeMapper = new ErrorsToHttpStatusCodeMapper(); } + [Fact] + public void should_return_timeout() + { + this.Given(x => x.GivenThereAreErrors(new List + { + new RequestTimedOutError(new Exception()) + })) + .When(x => x.WhenIGetErrorStatusCode()) + .Then(x => x.ThenTheResponseIsStatusCodeIs(503)) + .BDDfy(); + } + [Fact] public void should_create_unauthenticated_response_code() { diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index 7dae5e47..b3053afa 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -1,4 +1,5 @@ using Ocelot.Configuration; +using Ocelot.Configuration.Builder; using Ocelot.ServiceDiscovery; using Shouldly; using TestStack.BDDfy; @@ -20,7 +21,11 @@ namespace Ocelot.UnitTests.ServiceDiscovery [Fact] public void should_return_no_service_provider() { - var serviceConfig = new ServiceProviderConfiguraion("product", "127.0.0.1", 80, false, "Does not matter", string.Empty, 0); + var serviceConfig = new ServiceProviderConfiguraionBuilder() + .WithDownstreamHost("127.0.0.1") + .WithDownstreamPort(80) + .WithUseServiceDiscovery(false) + .Build(); this.Given(x => x.GivenTheReRoute(serviceConfig)) .When(x => x.WhenIGetTheServiceProvider()) @@ -31,7 +36,11 @@ namespace Ocelot.UnitTests.ServiceDiscovery [Fact] public void should_return_consul_service_provider() { - var serviceConfig = new ServiceProviderConfiguraion("product", string.Empty, 0, true, "Consul", string.Empty, 0); + var serviceConfig = new ServiceProviderConfiguraionBuilder() + .WithServiceName("product") + .WithUseServiceDiscovery(true) + .WithServiceDiscoveryProvider("Consul") + .Build(); this.Given(x => x.GivenTheReRoute(serviceConfig)) .When(x => x.WhenIGetTheServiceProvider())