diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index bc89f97cc..e6cb36951 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -125,20 +125,14 @@ is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them Dynamic Routing ^^^^^^^^^^^^^^^ -This feature was requested in `issue 340 `_. The idea is to enable dynamic routing when using -a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segmentof the upstream path to lookup the -downstream service with the service discovery provider. +This feature was requested in `issue 340 `_. The idea is to enable dynamic routing when using a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segment of the upstream path to lookup the downstream service with the service discovery provider. An example of this would be calling ocelot with a url like https://api.mywebsite.com/product/products. Ocelot will take the first segment of -the path which is product and use it as a key to look up the service in consul. If consul returns a service Ocelot will request it on whatever host and -port comes back from consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products. -Ocelot will apprend any query string to the downstream url as normal. +the path which is product and use it as a key to look up the service in consul. If consul returns a service Ocelot will request it on whatever host and port comes back from consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products. Ocelot will apprend any query string to the downstream url as normal. -In order to enable dynamic routing you need to have 0 ReRoutes in your config. At the moment you cannot mix dynamic and configuration ReRoutes. In addition to this you -need to specify the Service Discovery provider details as outlined above and the downstream http/https scheme as DownstreamScheme. +In order to enable dynamic routing you need to have 0 ReRoutes in your config. At the moment you cannot mix dynamic and configuration ReRoutes. In addition to this you need to specify the Service Discovery provider details as outlined above and the downstream http/https scheme as DownstreamScheme. -In addition to that you can set RateLimitOptions, QoSOptions, LoadBalancerOptions and HttpHandlerOptions, DownstreamScheme (You might want to call Ocelot on https but -talk to private services over http) that will be applied to all of the dynamic ReRoutes. +In addition to that you can set RateLimitOptions, QoSOptions, LoadBalancerOptions and HttpHandlerOptions, DownstreamScheme (You might want to call Ocelot on https but talk to private services over http) that will be applied to all of the dynamic ReRoutes. The config might look something like @@ -183,4 +177,40 @@ The config might look something like } } +Ocelot also allows you to set DynamicReRoutes which lets you set rate limiting rules per downstream service. This is useful if you have for example a product and search service and you want to rate limit one more than the other. An example of this would be as follows. + +.. code-block:: json + + { + "DynamicReRoutes": [ + { + "ServiceName": "product", + "RateLimitRule": { + "ClientWhitelist": [], + "EnableRateLimiting": true, + "Period": "1s", + "PeriodTimespan": 1000.0, + "Limit": 3 + } + } + ], + "GlobalConfiguration": { + "RequestIdKey": null, + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 8523, + }, + "RateLimitOptions": { + "ClientIdHeader": "ClientId", + "QuotaExceededMessage": "", + "RateLimitCounterPrefix": "", + "DisableRateLimitHeaders": false, + "HttpStatusCode": 428 + } + "DownstreamScheme": "http", + } + } + +This configuration means that if you have a request come into Ocelot on /product/* then dynamic routing will kick in and ocelot will use the rate limiting set against the product service in the DynamicReRoutes section. + Please take a look through all of the docs to understand these options. diff --git a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs index 769344344..d393b6483 100644 --- a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs @@ -103,6 +103,12 @@ private async Task> SetUpConfiguration(FileConf reRoutes.Add(ocelotReRoute); } + foreach(var fileDynamicReRoute in fileConfiguration.DynamicReRoutes) + { + var reRoute = SetUpDynamicReRoute(fileDynamicReRoute, fileConfiguration.GlobalConfiguration); + reRoutes.Add(reRoute); + } + var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration); var lbOptions = CreateLoadBalancerOptions(fileConfiguration.GlobalConfiguration.LoadBalancerOptions); @@ -124,7 +130,24 @@ private async Task> SetUpConfiguration(FileConf return new OkResponse(config); } - public ReRoute SetUpAggregateReRoute(List reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) + private ReRoute SetUpDynamicReRoute(FileDynamicReRoute fileDynamicReRoute, FileGlobalConfiguration globalConfiguration) + { + var rateLimitOption = _rateLimitOptionsCreator.Create(fileDynamicReRoute.RateLimitRule, globalConfiguration); + + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithEnableRateLimiting(true) + .WithRateLimitOptions(rateLimitOption) + .WithServiceName(fileDynamicReRoute.ServiceName) + .Build(); + + var reRoute = new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .Build(); + + return reRoute; + } + + private ReRoute SetUpAggregateReRoute(List reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) { var applicableReRoutes = reRoutes .SelectMany(x => x.DownstreamReRoute) @@ -186,7 +209,7 @@ private DownstreamReRoute SetUpDownstreamReRoute(FileReRoute fileReRoute, FileGl var qosOptions = _qosOptionsCreator.Create(fileReRoute.QoSOptions, fileReRoute.UpstreamPathTemplate, fileReRoute.UpstreamHttpMethod.ToArray()); - var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting); + var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute.RateLimitOptions, globalConfiguration); var region = _regionCreator.Create(fileReRoute); diff --git a/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs b/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs index 91beea740..e90967867 100644 --- a/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs @@ -1,9 +1,9 @@ -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public interface IRateLimitOptionsCreator - { - RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting); - } -} \ No newline at end of file +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IRateLimitOptionsCreator + { + RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration); + } +} diff --git a/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs b/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs index 5d987aeee..10e63f6f7 100644 --- a/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs @@ -1,32 +1,32 @@ -using System; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public class RateLimitOptionsCreator : IRateLimitOptionsCreator - { - public RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting) - { - RateLimitOptions rateLimitOption = null; - - if (enableRateLimiting) - { - rateLimitOption = new RateLimitOptionsBuilder() - .WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader) - .WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) - .WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders) - .WithEnableRateLimiting(fileReRoute.RateLimitOptions.EnableRateLimiting) - .WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode) - .WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage) - .WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix) - .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, - fileReRoute.RateLimitOptions.PeriodTimespan, - fileReRoute.RateLimitOptions.Limit)) - .Build(); - } - - return rateLimitOption; - } - } -} +using System; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class RateLimitOptionsCreator : IRateLimitOptionsCreator + { + public RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration) + { + RateLimitOptions rateLimitOption = null; + + if (fileRateLimitRule != null && fileRateLimitRule.EnableRateLimiting) + { + rateLimitOption = new RateLimitOptionsBuilder() + .WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader) + .WithClientWhiteList(fileRateLimitRule.ClientWhitelist) + .WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders) + .WithEnableRateLimiting(fileRateLimitRule.EnableRateLimiting) + .WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode) + .WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage) + .WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix) + .WithRateLimitRule(new RateLimitRule(fileRateLimitRule.Period, + fileRateLimitRule.PeriodTimespan, + fileRateLimitRule.Limit)) + .Build(); + } + + return rateLimitOption; + } + } +} diff --git a/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs b/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs index c777a50a8..3a8d1811c 100644 --- a/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs @@ -1,45 +1,45 @@ -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public class ReRouteOptionsCreator : IReRouteOptionsCreator - { - public ReRouteOptions Create(FileReRoute fileReRoute) - { - var isAuthenticated = IsAuthenticated(fileReRoute); - var isAuthorised = IsAuthorised(fileReRoute); - var isCached = IsCached(fileReRoute); - var enableRateLimiting = IsEnableRateLimiting(fileReRoute); - - var options = new ReRouteOptionsBuilder() - .WithIsAuthenticated(isAuthenticated) - .WithIsAuthorised(isAuthorised) - .WithIsCached(isCached) - .WithRateLimiting(enableRateLimiting) - .Build(); - - return options; - } - - private static bool IsEnableRateLimiting(FileReRoute fileReRoute) - { - return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false; - } - - private bool IsAuthenticated(FileReRoute fileReRoute) - { - return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.AuthenticationProviderKey); - } - - private bool IsAuthorised(FileReRoute fileReRoute) - { - return fileReRoute.RouteClaimsRequirement?.Count > 0; - } - - private bool IsCached(FileReRoute fileReRoute) - { - return fileReRoute.FileCacheOptions.TtlSeconds > 0; - } +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class ReRouteOptionsCreator : IReRouteOptionsCreator + { + public ReRouteOptions Create(FileReRoute fileReRoute) + { + var isAuthenticated = IsAuthenticated(fileReRoute); + var isAuthorised = IsAuthorised(fileReRoute); + var isCached = IsCached(fileReRoute); + var enableRateLimiting = IsEnableRateLimiting(fileReRoute); + + var options = new ReRouteOptionsBuilder() + .WithIsAuthenticated(isAuthenticated) + .WithIsAuthorised(isAuthorised) + .WithIsCached(isCached) + .WithRateLimiting(enableRateLimiting) + .Build(); + + return options; + } + + private static bool IsEnableRateLimiting(FileReRoute fileReRoute) + { + return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false; + } + + private bool IsAuthenticated(FileReRoute fileReRoute) + { + return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.AuthenticationProviderKey); + } + + private bool IsAuthorised(FileReRoute fileReRoute) + { + return fileReRoute.RouteClaimsRequirement?.Count > 0; + } + + private bool IsCached(FileReRoute fileReRoute) + { + return fileReRoute.FileCacheOptions.TtlSeconds > 0; + } } -} +} diff --git a/src/Ocelot/Configuration/File/FileConfiguration.cs b/src/Ocelot/Configuration/File/FileConfiguration.cs index e38ae6cbd..49eb0c9e5 100644 --- a/src/Ocelot/Configuration/File/FileConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileConfiguration.cs @@ -1,20 +1,22 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration.File -{ - public class FileConfiguration - { - public FileConfiguration() - { - ReRoutes = new List(); - GlobalConfiguration = new FileGlobalConfiguration(); - Aggregates = new List(); - } - - public List ReRoutes { get; set; } - - // Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates - public List Aggregates { get;set; } - public FileGlobalConfiguration GlobalConfiguration { get; set; } - } -} +using System.Collections.Generic; + +namespace Ocelot.Configuration.File +{ + public class FileConfiguration + { + public FileConfiguration() + { + ReRoutes = new List(); + GlobalConfiguration = new FileGlobalConfiguration(); + Aggregates = new List(); + DynamicReRoutes = new List(); + } + + public List ReRoutes { get; set; } + public List DynamicReRoutes { get; set; } + + // Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates + public List Aggregates { get;set; } + public FileGlobalConfiguration GlobalConfiguration { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileDynamicReRoute.cs b/src/Ocelot/Configuration/File/FileDynamicReRoute.cs new file mode 100644 index 000000000..26d8b4d4f --- /dev/null +++ b/src/Ocelot/Configuration/File/FileDynamicReRoute.cs @@ -0,0 +1,8 @@ +namespace Ocelot.Configuration.File +{ + public class FileDynamicReRoute + { + public string ServiceName { get; set; } + public FileRateLimitRule RateLimitRule { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs index c7f99cb0b..05c6fac41 100644 --- a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs +++ b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs @@ -1,37 +1,37 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.Configuration.File -{ - public class FileRateLimitOptions - { - /// - /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId - /// - public string ClientIdHeader { get; set; } = "ClientId"; - - /// - /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. - /// If none specified the default will be: - /// API calls quota exceeded! maximum admitted {0} per {1} - /// - public string QuotaExceededMessage { get; set; } - - /// - /// Gets or sets the counter prefix, used to compose the rate limit counter cache key - /// - public string RateLimitCounterPrefix { get; set; } = "ocelot"; - - /// - /// Disables X-Rate-Limit and Rety-After headers - /// - public bool DisableRateLimitHeaders { get; set; } - - /// - /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) - /// - public int HttpStatusCode { get; set; } = 429; - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Configuration.File +{ + public class FileRateLimitOptions + { + /// + /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId + /// + public string ClientIdHeader { get; set; } = "ClientId"; + + /// + /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. + /// If none specified the default will be: + /// API calls quota exceeded! maximum admitted {0} per {1} + /// + public string QuotaExceededMessage { get; set; } + + /// + /// Gets or sets the counter prefix, used to compose the rate limit counter cache key + /// + public string RateLimitCounterPrefix { get; set; } = "ocelot"; + + /// + /// Disables X-Rate-Limit and Rety-After headers + /// + public bool DisableRateLimitHeaders { get; set; } + + /// + /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) + /// + public int HttpStatusCode { get; set; } = 429; + } +} diff --git a/src/Ocelot/Configuration/File/FileRateLimitRule.cs b/src/Ocelot/Configuration/File/FileRateLimitRule.cs index 7d1ca1ef4..6ebdb2bd7 100644 --- a/src/Ocelot/Configuration/File/FileRateLimitRule.cs +++ b/src/Ocelot/Configuration/File/FileRateLimitRule.cs @@ -1,52 +1,52 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Ocelot.Infrastructure.Extensions; - -namespace Ocelot.Configuration.File -{ - public class FileRateLimitRule - { - public FileRateLimitRule() - { - ClientWhitelist = new List(); - } - - public List ClientWhitelist { get; set; } - - /// - /// Enables endpoint rate limiting based URL path and HTTP verb - /// - public bool EnableRateLimiting { get; set; } - - /// - /// Rate limit period as in 1s, 1m, 1h - /// - public string Period { get; set; } - - public double PeriodTimespan { get; set; } - - /// - /// Maximum number of requests that a client can make in a defined period - /// - public long Limit { get; set; } - - public override string ToString() - { - if (!EnableRateLimiting) - { - return string.Empty; - } - - var sb = new StringBuilder(); - sb.Append( - $"{nameof(Period)}:{Period},{nameof(PeriodTimespan)}:{PeriodTimespan:F},{nameof(Limit)}:{Limit},{nameof(ClientWhitelist)}:["); - - sb.AppendJoin(',', ClientWhitelist); - sb.Append(']'); - return sb.ToString(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Ocelot.Infrastructure.Extensions; + +namespace Ocelot.Configuration.File +{ + public class FileRateLimitRule + { + public FileRateLimitRule() + { + ClientWhitelist = new List(); + } + + public List ClientWhitelist { get; set; } + + /// + /// Enables endpoint rate limiting based URL path and HTTP verb + /// + public bool EnableRateLimiting { get; set; } + + /// + /// Rate limit period as in 1s, 1m, 1h + /// + public string Period { get; set; } + + public double PeriodTimespan { get; set; } + + /// + /// Maximum number of requests that a client can make in a defined period + /// + public long Limit { get; set; } + + public override string ToString() + { + if (!EnableRateLimiting) + { + return string.Empty; + } + + var sb = new StringBuilder(); + sb.Append( + $"{nameof(Period)}:{Period},{nameof(PeriodTimespan)}:{PeriodTimespan:F},{nameof(Limit)}:{Limit},{nameof(ClientWhitelist)}:["); + + sb.AppendJoin(',', ClientWhitelist); + sb.Append(']'); + return sb.ToString(); + } + } +} diff --git a/src/Ocelot/Configuration/ReRouteOptions.cs b/src/Ocelot/Configuration/ReRouteOptions.cs index 13875ac8f..074e45b63 100644 --- a/src/Ocelot/Configuration/ReRouteOptions.cs +++ b/src/Ocelot/Configuration/ReRouteOptions.cs @@ -1,18 +1,18 @@ -namespace Ocelot.Configuration -{ - public class ReRouteOptions - { - public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isEnableRateLimiting) - { - IsAuthenticated = isAuthenticated; - IsAuthorised = isAuthorised; - IsCached = isCached; - EnableRateLimiting = isEnableRateLimiting; - } - - public bool IsAuthenticated { get; private set; } - public bool IsAuthorised { get; private set; } - public bool IsCached { get; private set; } - public bool EnableRateLimiting { get; private set; } - } -} +namespace Ocelot.Configuration +{ + public class ReRouteOptions + { + public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isEnableRateLimiting) + { + IsAuthenticated = isAuthenticated; + IsAuthorised = isAuthorised; + IsCached = isCached; + EnableRateLimiting = isEnableRateLimiting; + } + + public bool IsAuthenticated { get; private set; } + public bool IsAuthorised { get; private set; } + public bool IsCached { get; private set; } + public bool EnableRateLimiting { get; private set; } + } +} diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs index 9a90edaf9..f7dea902c 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs @@ -2,6 +2,7 @@ { using System.Collections.Concurrent; using System.Collections.Generic; + using System.Linq; using Configuration; using Configuration.Builder; using Configuration.Creator; @@ -43,7 +44,7 @@ public Response Get(string upstreamUrlPath, string upstreamQuer var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new []{ upstreamHttpMethod }); - var downstreamReRoute = new DownstreamReRouteBuilder() + var downstreamReRouteBuilder = new DownstreamReRouteBuilder() .WithServiceName(serviceName) .WithLoadBalancerKey(loadBalancerKey) .WithDownstreamPathTemplate(downstreamPath) @@ -51,8 +52,22 @@ public Response Get(string upstreamUrlPath, string upstreamQuer .WithHttpHandlerOptions(configuration.HttpHandlerOptions) .WithQosOptions(qosOptions) .WithDownstreamScheme(configuration.DownstreamScheme) - .WithLoadBalancerOptions(configuration.LoadBalancerOptions) - .Build(); + .WithLoadBalancerOptions(configuration.LoadBalancerOptions); + + var rateLimitOptions = configuration.ReRoutes != null + ? configuration.ReRoutes + .SelectMany(x => x.DownstreamReRoute) + .FirstOrDefault(x => x.ServiceName == serviceName) + : null; + + if(rateLimitOptions != null) + { + downstreamReRouteBuilder + .WithRateLimitOptions(rateLimitOptions.RateLimitOptions) + .WithEnableRateLimiting(true); + } + + var downstreamReRoute = downstreamReRouteBuilder.Build(); var reRoute = new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs index fc74f679c..080172d55 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs @@ -20,7 +20,9 @@ public DownstreamRouteProviderFactory(IServiceProvider provider, IOcelotLoggerFa public IDownstreamRouteProvider Get(IInternalConfiguration config) { - if(!config.ReRoutes.Any() && IsServiceDiscovery(config.ServiceProviderConfiguration)) + //todo - this is a bit hacky we are saying there are no reRoutes or there are reRoutes but none of them have + //an upstream path template which means they are dyanmic and service discovery is on... + if((!config.ReRoutes.Any() || config.ReRoutes.All(x => string.IsNullOrEmpty(x.UpstreamPathTemplate.Value))) && IsServiceDiscovery(config.ServiceProviderConfiguration)) { _logger.LogInformation($"Selected {nameof(DownstreamRouteCreator)} as DownstreamRouteProvider for this request"); return _providers[nameof(DownstreamRouteCreator)]; diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index b6c4bcf6a..3916721db 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -52,6 +52,7 @@ public async Task Invoke(DownstreamContext context) } var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value)); + Logger.LogDebug($"downstream templates are {downstreamPathTemplates}"); context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues; diff --git a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs index 23e6dd623..d7d31abe4 100644 --- a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs +++ b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs @@ -42,7 +42,6 @@ public void should_call_withratelimiting() UpstreamPathTemplate = "/api/ClientRateLimit", UpstreamHttpMethod = new List { "Get" }, RequestIdKey = _steps.RequestIdKey, - RateLimitOptions = new FileRateLimitRule() { EnableRateLimiting = true, diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs index af99888dc..23ce3d117 100644 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net; using System.Text; +using Consul; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -22,9 +23,11 @@ public class ConfigurationInConsulTests : IDisposable private readonly Steps _steps; private IWebHost _fakeConsulBuilder; private FileConfiguration _config; + private List _consulServices; public ConfigurationInConsulTests() { + _consulServices = new List(); _steps = new Steps(); } @@ -63,7 +66,7 @@ public void should_return_response_200_with_simple_url() var fakeConsulServiceDiscoveryUrl = "http://localhost:9500"; - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, "")) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) @@ -108,7 +111,7 @@ public void should_return_response_200_with_simple_url_when_using_jsonserialized var fakeConsulServiceDiscoveryUrl = "http://localhost:9502"; - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, "")) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache()) @@ -168,7 +171,7 @@ public void should_load_configuration_out_of_consul() }; this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) - .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, "")) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) @@ -257,7 +260,7 @@ public void should_load_configuration_out_of_consul_if_it_is_changed() }; this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) - .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, "")) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51780", "/status", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) @@ -269,6 +272,89 @@ public void should_load_configuration_out_of_consul_if_it_is_changed() .BDDfy(); } + [Fact] + public void should_handle_request_to_consul_for_downstream_service_and_make_request_no_re_routes_and_rate_limit() + { + const int consulPort = 8523; + const string serviceName = "web"; + const int downstreamServicePort = 8187; + var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}"; + var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; + var serviceEntryOne = new ServiceEntry() + { + Service = new AgentService() + { + Service = serviceName, + Address = "localhost", + Port = downstreamServicePort, + ID = "web_90_0_2_224_8080", + Tags = new[] {"version-v1"} + }, + }; + + var consulConfig = new FileConfiguration + { + DynamicReRoutes = new List + { + new FileDynamicReRoute + { + ServiceName = serviceName, + RateLimitRule = new FileRateLimitRule() + { + EnableRateLimiting = true, + ClientWhitelist = new List(), + Limit = 3, + Period = "1s", + PeriodTimespan = 1000 + } + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Port = consulPort + }, + RateLimitOptions = new FileRateLimitOptions() + { + ClientIdHeader = "ClientId", + DisableRateLimitHeaders = false, + QuotaExceededMessage = "", + RateLimitCounterPrefix = "", + HttpStatusCode = 428 + }, + DownstreamScheme = "http", + } + }; + + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Port = consulPort + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/something", 200, "Hello from Laura")) + .And(x => GivenTheConsulConfigurationIs(consulConfig)) + .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) + .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something",1)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something", 2)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something",1)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(428)) + .BDDfy(); + } + private void ThenTheConfigIsUpdatedInOcelot() { var result = WaitFor(20000).Until(() => { @@ -292,7 +378,15 @@ private void GivenTheConsulConfigurationIs(FileConfiguration config) _config = config; } - private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) + private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries) + { + foreach(var serviceEntry in serviceEntries) + { + _consulServices.Add(serviceEntry); + } + } + + private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName) { _fakeConsulBuilder = new WebHostBuilder() .UseUrls(url) @@ -336,6 +430,10 @@ private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) throw; } } + else if (context.Request.Path.Value == $"/v1/health/service/{serviceName}") + { + await context.Response.WriteJsonAsync(_consulServices); + } }); }) .Build(); diff --git a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs index af7dfed7a..b9d4a422e 100644 --- a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs @@ -17,6 +17,7 @@ using Ocelot.Errors; using Ocelot.UnitTests.TestData; using Ocelot.Values; + using System; public class FileInternalConfigurationCreatorTests { @@ -821,6 +822,54 @@ public void should_return_validation_errors() .BDDfy(); } + [Fact] + public void should_set_up_dynamic_re_routes() + { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithEnableRateLimiting(true) + .WithRateLimitOptions(new RateLimitOptionsBuilder().Build()) + .Build(); + + var rateLimitOptions = new RateLimitOptionsBuilder() + .WithRateLimitRule(new RateLimitRule("1s", 1, 1)) + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + DynamicReRoutes = new List + { + new FileDynamicReRoute + { + ServiceName = "test", + RateLimitRule = new FileRateLimitRule + { + Period = "1s", + PeriodTimespan = 1, + Limit = 1 + } + } + }, + })) + .And(x => x.GivenTheConfigIsValid()) + .And(x => GivenTheRateLimitCreatorReturns(rateLimitOptions)) + .And(x => GivenTheDownstreamAddresses()) + .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheDynamicReRouteIsSetUp("test", rateLimitOptions.RateLimitRule)) + .BDDfy(); + } + + private void GivenTheRateLimitCreatorReturns(RateLimitOptions rateLimitOptions) + { + _rateLimitOptions + .Setup(x => x.Create(It.IsAny(), It.IsAny())) + .Returns(rateLimitOptions); + } + private void GivenTheConfigIsInvalid(List errors) { _validator @@ -844,7 +893,7 @@ private void GivenTheFollowingOptionsAreReturned(ReRouteOptions fileReRouteOptio private void ThenTheRateLimitOptionsCreatorIsCalledCorrectly() { _rateLimitOptions - .Verify(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + .Verify(x => x.Create(It.IsAny(), It.IsAny()), Times.Once); } private void GivenTheConfigIsValid() @@ -864,6 +913,16 @@ private void WhenICreateTheConfig() _config = _internalConfigurationCreator.Create(_fileConfiguration).Result; } + private void ThenTheDynamicReRouteIsSetUp(string serviceName, RateLimitRule rateLimitOptions) + { + var dynamic = _config.Data.ReRoutes[0].DownstreamReRoute[0]; + dynamic.ServiceName.ShouldBe(serviceName); + dynamic.EnableEndpointEndpointRateLimiting.ShouldBeTrue(); + dynamic.RateLimitOptions.RateLimitRule.Period.ShouldBe(rateLimitOptions.Period); + dynamic.RateLimitOptions.RateLimitRule.Limit.ShouldBe(rateLimitOptions.Limit); + dynamic.RateLimitOptions.RateLimitRule.PeriodTimespan.ShouldBe(rateLimitOptions.PeriodTimespan); + } + private void ThenTheReRoutesAre(List expectedReRoutes) { for (int i = 0; i < _config.Data.ReRoutes.Count; i++) diff --git a/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs index 69093cca2..e77e3a62c 100644 --- a/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs @@ -1,108 +1,108 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class RateLimitOptionsCreatorTests - { - private FileReRoute _fileReRoute; - private FileGlobalConfiguration _fileGlobalConfig; - private bool _enabled; - private RateLimitOptionsCreator _creator; - private RateLimitOptions _result; - - public RateLimitOptionsCreatorTests() - { - _creator = new RateLimitOptionsCreator(); - } - - [Fact] - public void should_create_rate_limit_options() - { - var fileReRoute = new FileReRoute - { - RateLimitOptions = new FileRateLimitRule - { - ClientWhitelist = new List(), - Period = "Period", - Limit = 1, - PeriodTimespan = 1, - EnableRateLimiting = true - } - }; - var fileGlobalConfig = new FileGlobalConfiguration - { - RateLimitOptions = new FileRateLimitOptions - { - ClientIdHeader = "ClientIdHeader", - DisableRateLimitHeaders = true, - QuotaExceededMessage = "QuotaExceededMessage", - RateLimitCounterPrefix = "RateLimitCounterPrefix", - HttpStatusCode = 200 - } - }; - var expected = new RateLimitOptionsBuilder() - .WithClientIdHeader("ClientIdHeader") - .WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) - .WithDisableRateLimitHeaders(true) - .WithEnableRateLimiting(true) - .WithHttpStatusCode(200) - .WithQuotaExceededMessage("QuotaExceededMessage") - .WithRateLimitCounterPrefix("RateLimitCounterPrefix") - .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, - fileReRoute.RateLimitOptions.PeriodTimespan, - fileReRoute.RateLimitOptions.Limit)) - .Build(); - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .And(x => x.GivenTheFollowingFileGlobalConfig(fileGlobalConfig)) - .And(x => x.GivenRateLimitingIsEnabled()) - .When(x => x.WhenICreate()) - .Then(x => x.ThenTheFollowingIsReturned(expected)) - .BDDfy(); - } - - private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute) - { - _fileReRoute = fileReRoute; - } - - private void GivenTheFollowingFileGlobalConfig(FileGlobalConfiguration fileGlobalConfig) - { - _fileGlobalConfig = fileGlobalConfig; - } - - private void GivenRateLimitingIsEnabled() - { - _enabled = true; - } - - private void WhenICreate() - { - _result = _creator.Create(_fileReRoute, _fileGlobalConfig, _enabled); - } - - private void ThenTheFollowingIsReturned(RateLimitOptions expected) - { - _result.ClientIdHeader.ShouldBe(expected.ClientIdHeader); - _result.ClientWhitelist.ShouldBe(expected.ClientWhitelist); - _result.DisableRateLimitHeaders.ShouldBe(expected.DisableRateLimitHeaders); - _result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); - _result.HttpStatusCode.ShouldBe(expected.HttpStatusCode); - _result.QuotaExceededMessage.ShouldBe(expected.QuotaExceededMessage); - _result.RateLimitCounterPrefix.ShouldBe(expected.RateLimitCounterPrefix); - _result.RateLimitRule.Limit.ShouldBe(expected.RateLimitRule.Limit); - _result.RateLimitRule.Period.ShouldBe(expected.RateLimitRule.Period); - TimeSpan.FromSeconds(_result.RateLimitRule.PeriodTimespan).Ticks.ShouldBe(TimeSpan.FromSeconds(expected.RateLimitRule.PeriodTimespan).Ticks); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class RateLimitOptionsCreatorTests + { + private FileReRoute _fileReRoute; + private FileGlobalConfiguration _fileGlobalConfig; + private bool _enabled; + private RateLimitOptionsCreator _creator; + private RateLimitOptions _result; + + public RateLimitOptionsCreatorTests() + { + _creator = new RateLimitOptionsCreator(); + } + + [Fact] + public void should_create_rate_limit_options() + { + var fileReRoute = new FileReRoute + { + RateLimitOptions = new FileRateLimitRule + { + ClientWhitelist = new List(), + Period = "Period", + Limit = 1, + PeriodTimespan = 1, + EnableRateLimiting = true + } + }; + var fileGlobalConfig = new FileGlobalConfiguration + { + RateLimitOptions = new FileRateLimitOptions + { + ClientIdHeader = "ClientIdHeader", + DisableRateLimitHeaders = true, + QuotaExceededMessage = "QuotaExceededMessage", + RateLimitCounterPrefix = "RateLimitCounterPrefix", + HttpStatusCode = 200 + } + }; + var expected = new RateLimitOptionsBuilder() + .WithClientIdHeader("ClientIdHeader") + .WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) + .WithDisableRateLimitHeaders(true) + .WithEnableRateLimiting(true) + .WithHttpStatusCode(200) + .WithQuotaExceededMessage("QuotaExceededMessage") + .WithRateLimitCounterPrefix("RateLimitCounterPrefix") + .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, + fileReRoute.RateLimitOptions.PeriodTimespan, + fileReRoute.RateLimitOptions.Limit)) + .Build(); + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .And(x => x.GivenTheFollowingFileGlobalConfig(fileGlobalConfig)) + .And(x => x.GivenRateLimitingIsEnabled()) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheFollowingIsReturned(expected)) + .BDDfy(); + } + + private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute) + { + _fileReRoute = fileReRoute; + } + + private void GivenTheFollowingFileGlobalConfig(FileGlobalConfiguration fileGlobalConfig) + { + _fileGlobalConfig = fileGlobalConfig; + } + + private void GivenRateLimitingIsEnabled() + { + _enabled = true; + } + + private void WhenICreate() + { + _result = _creator.Create(_fileReRoute.RateLimitOptions, _fileGlobalConfig); + } + + private void ThenTheFollowingIsReturned(RateLimitOptions expected) + { + _result.ClientIdHeader.ShouldBe(expected.ClientIdHeader); + _result.ClientWhitelist.ShouldBe(expected.ClientWhitelist); + _result.DisableRateLimitHeaders.ShouldBe(expected.DisableRateLimitHeaders); + _result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); + _result.HttpStatusCode.ShouldBe(expected.HttpStatusCode); + _result.QuotaExceededMessage.ShouldBe(expected.QuotaExceededMessage); + _result.RateLimitCounterPrefix.ShouldBe(expected.RateLimitCounterPrefix); + _result.RateLimitRule.Limit.ShouldBe(expected.RateLimitRule.Limit); + _result.RateLimitRule.Period.ShouldBe(expected.RateLimitRule.Period); + TimeSpan.FromSeconds(_result.RateLimitRule.PeriodTimespan).Ticks.ShouldBe(TimeSpan.FromSeconds(expected.RateLimitRule.PeriodTimespan).Ticks); + } + } +} diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs index b29d2d29c..a1c4b7993 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs @@ -1,9 +1,3 @@ -using Ocelot.DownstreamRouteFinder.Finder; -using Xunit; -using Shouldly; -using Ocelot.Configuration; -using System.Net.Http; - namespace Ocelot.UnitTests.DownstreamRouteFinder { using System; @@ -14,6 +8,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder using Ocelot.LoadBalancer.LoadBalancers; using Responses; using TestStack.BDDfy; + using Ocelot.DownstreamRouteFinder.Finder; + using Xunit; + using Shouldly; + using Ocelot.Configuration; + using System.Net.Http; + using System.Collections.Generic; public class DownstreamRouteCreatorTests { @@ -53,6 +53,34 @@ public void should_create_downstream_route() .BDDfy(); } + [Fact] + public void should_create_downstream_route_with_rate_limit_options() + { + var rateLimitOptions = new RateLimitOptionsBuilder() + .WithEnableRateLimiting(true) + .WithClientIdHeader("test") + .Build(); + + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithServiceName("auth") + .WithRateLimitOptions(rateLimitOptions) + .Build(); + + var reRoute = new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .Build(); + + var reRoutes = new List { reRoute }; + + var configuration = new InternalConfiguration(reRoutes, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions); + + this.Given(_ => GivenTheConfiguration(configuration)) + .When(_ => WhenICreate()) + .Then(_ => ThenTheDownstreamRouteIsCreated()) + .And(_ => WithRateLimitOptions(rateLimitOptions)) + .BDDfy(); + } + [Fact] public void should_cache_downstream_route() { @@ -174,6 +202,13 @@ private void GivenTheQosCreatorReturns(QoSOptions options) .Returns(options); } + private void WithRateLimitOptions(RateLimitOptions expected) + { + _result.Data.ReRoute.DownstreamReRoute[0].EnableEndpointEndpointRateLimiting.ShouldBeTrue(); + _result.Data.ReRoute.DownstreamReRoute[0].RateLimitOptions.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); + _result.Data.ReRoute.DownstreamReRoute[0].RateLimitOptions.ClientIdHeader.ShouldBe(expected.ClientIdHeader); + } + private void ThenTheDownstreamRouteIsCreated() { _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test"); diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs index 2b5956607..0380f2fcc 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs @@ -53,6 +53,21 @@ public void should_return_downstream_route_finder() .BDDfy(); } + [Fact] + public void should_return_downstream_route_finder_when_not_dynamic_re_route_and_service_discovery_on() + { + var spConfig = new ServiceProviderConfigurationBuilder().WithHost("test").WithPort(50).WithType("test").Build(); + var reRoutes = new List + { + new ReRouteBuilder().WithUpstreamPathTemplate("woot").Build() + }; + + this.Given(_ => GivenTheReRoutes(reRoutes, spConfig)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheResultShouldBe()) + .BDDfy(); + } + [Fact] public void should_return_downstream_route_finder_as_no_service_discovery_given_no_host() { @@ -101,6 +116,21 @@ public void should_return_downstream_route_creator() .BDDfy(); } + [Fact] + public void should_return_downstream_route_creator_with_dynamic_re_route() + { + var spConfig = new ServiceProviderConfigurationBuilder().WithHost("test").WithPort(50).WithType("test").Build(); + var reRoutes = new List + { + new ReRouteBuilder().Build() + }; + + this.Given(_ => GivenTheReRoutes(reRoutes, spConfig)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheResultShouldBe()) + .BDDfy(); + } + private void ThenTheResultShouldBe() { _result.ShouldBeOfType();