mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 18:22:49 +08:00
Feature/more dynamic routes (#508)
* Made the file config poller use IHostedService, bit more generic, not just need to provide the correct implementations of the repo services and it will poll anything..this means we can open up redis for #458 * removed comments * #458 allow users to set rate limits per service for dynamic re routes * #458 added docs for rate limting on dynamic reroutes
This commit is contained in:
parent
0f2cf2d188
commit
b0a20d13b9
@ -125,20 +125,14 @@ is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them
|
|||||||
Dynamic Routing
|
Dynamic Routing
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This feature was requested in `issue 340 <https://github.com/TomPallister/Ocelot/issue/340>`_. The idea is to enable dynamic routing when using
|
This feature was requested in `issue 340 <https://github.com/TomPallister/Ocelot/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.
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
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
|
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.
|
||||||
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
|
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.
|
||||||
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
|
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.
|
||||||
talk to private services over http) that will be applied to all of the dynamic ReRoutes.
|
|
||||||
|
|
||||||
The config might look something like
|
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.
|
Please take a look through all of the docs to understand these options.
|
||||||
|
@ -103,6 +103,12 @@ namespace Ocelot.Configuration.Creator
|
|||||||
reRoutes.Add(ocelotReRoute);
|
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 serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration);
|
||||||
|
|
||||||
var lbOptions = CreateLoadBalancerOptions(fileConfiguration.GlobalConfiguration.LoadBalancerOptions);
|
var lbOptions = CreateLoadBalancerOptions(fileConfiguration.GlobalConfiguration.LoadBalancerOptions);
|
||||||
@ -124,7 +130,24 @@ namespace Ocelot.Configuration.Creator
|
|||||||
return new OkResponse<IInternalConfiguration>(config);
|
return new OkResponse<IInternalConfiguration>(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReRoute SetUpAggregateReRoute(List<ReRoute> 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<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration)
|
||||||
{
|
{
|
||||||
var applicableReRoutes = reRoutes
|
var applicableReRoutes = reRoutes
|
||||||
.SelectMany(x => x.DownstreamReRoute)
|
.SelectMany(x => x.DownstreamReRoute)
|
||||||
@ -186,7 +209,7 @@ namespace Ocelot.Configuration.Creator
|
|||||||
|
|
||||||
var qosOptions = _qosOptionsCreator.Create(fileReRoute.QoSOptions, fileReRoute.UpstreamPathTemplate, fileReRoute.UpstreamHttpMethod.ToArray());
|
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);
|
var region = _regionCreator.Create(fileReRoute);
|
||||||
|
|
||||||
|
@ -4,6 +4,6 @@ namespace Ocelot.Configuration.Creator
|
|||||||
{
|
{
|
||||||
public interface IRateLimitOptionsCreator
|
public interface IRateLimitOptionsCreator
|
||||||
{
|
{
|
||||||
RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting);
|
RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,23 +6,23 @@ namespace Ocelot.Configuration.Creator
|
|||||||
{
|
{
|
||||||
public class RateLimitOptionsCreator : IRateLimitOptionsCreator
|
public class RateLimitOptionsCreator : IRateLimitOptionsCreator
|
||||||
{
|
{
|
||||||
public RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting)
|
public RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration)
|
||||||
{
|
{
|
||||||
RateLimitOptions rateLimitOption = null;
|
RateLimitOptions rateLimitOption = null;
|
||||||
|
|
||||||
if (enableRateLimiting)
|
if (fileRateLimitRule != null && fileRateLimitRule.EnableRateLimiting)
|
||||||
{
|
{
|
||||||
rateLimitOption = new RateLimitOptionsBuilder()
|
rateLimitOption = new RateLimitOptionsBuilder()
|
||||||
.WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader)
|
.WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader)
|
||||||
.WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist)
|
.WithClientWhiteList(fileRateLimitRule.ClientWhitelist)
|
||||||
.WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders)
|
.WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders)
|
||||||
.WithEnableRateLimiting(fileReRoute.RateLimitOptions.EnableRateLimiting)
|
.WithEnableRateLimiting(fileRateLimitRule.EnableRateLimiting)
|
||||||
.WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode)
|
.WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode)
|
||||||
.WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage)
|
.WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage)
|
||||||
.WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix)
|
.WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix)
|
||||||
.WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period,
|
.WithRateLimitRule(new RateLimitRule(fileRateLimitRule.Period,
|
||||||
fileReRoute.RateLimitOptions.PeriodTimespan,
|
fileRateLimitRule.PeriodTimespan,
|
||||||
fileReRoute.RateLimitOptions.Limit))
|
fileRateLimitRule.Limit))
|
||||||
.Build();
|
.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,9 +9,11 @@ namespace Ocelot.Configuration.File
|
|||||||
ReRoutes = new List<FileReRoute>();
|
ReRoutes = new List<FileReRoute>();
|
||||||
GlobalConfiguration = new FileGlobalConfiguration();
|
GlobalConfiguration = new FileGlobalConfiguration();
|
||||||
Aggregates = new List<FileAggregateReRoute>();
|
Aggregates = new List<FileAggregateReRoute>();
|
||||||
|
DynamicReRoutes = new List<FileDynamicReRoute>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FileReRoute> ReRoutes { get; set; }
|
public List<FileReRoute> ReRoutes { get; set; }
|
||||||
|
public List<FileDynamicReRoute> DynamicReRoutes { get; set; }
|
||||||
|
|
||||||
// Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates
|
// Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates
|
||||||
public List<FileAggregateReRoute> Aggregates { get;set; }
|
public List<FileAggregateReRoute> Aggregates { get;set; }
|
||||||
|
8
src/Ocelot/Configuration/File/FileDynamicReRoute.cs
Normal file
8
src/Ocelot/Configuration/File/FileDynamicReRoute.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace Ocelot.Configuration.File
|
||||||
|
{
|
||||||
|
public class FileDynamicReRoute
|
||||||
|
{
|
||||||
|
public string ServiceName { get; set; }
|
||||||
|
public FileRateLimitRule RateLimitRule { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Configuration;
|
using Configuration;
|
||||||
using Configuration.Builder;
|
using Configuration.Builder;
|
||||||
using Configuration.Creator;
|
using Configuration.Creator;
|
||||||
@ -43,7 +44,7 @@
|
|||||||
|
|
||||||
var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new []{ upstreamHttpMethod });
|
var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new []{ upstreamHttpMethod });
|
||||||
|
|
||||||
var downstreamReRoute = new DownstreamReRouteBuilder()
|
var downstreamReRouteBuilder = new DownstreamReRouteBuilder()
|
||||||
.WithServiceName(serviceName)
|
.WithServiceName(serviceName)
|
||||||
.WithLoadBalancerKey(loadBalancerKey)
|
.WithLoadBalancerKey(loadBalancerKey)
|
||||||
.WithDownstreamPathTemplate(downstreamPath)
|
.WithDownstreamPathTemplate(downstreamPath)
|
||||||
@ -51,8 +52,22 @@
|
|||||||
.WithHttpHandlerOptions(configuration.HttpHandlerOptions)
|
.WithHttpHandlerOptions(configuration.HttpHandlerOptions)
|
||||||
.WithQosOptions(qosOptions)
|
.WithQosOptions(qosOptions)
|
||||||
.WithDownstreamScheme(configuration.DownstreamScheme)
|
.WithDownstreamScheme(configuration.DownstreamScheme)
|
||||||
.WithLoadBalancerOptions(configuration.LoadBalancerOptions)
|
.WithLoadBalancerOptions(configuration.LoadBalancerOptions);
|
||||||
.Build();
|
|
||||||
|
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()
|
var reRoute = new ReRouteBuilder()
|
||||||
.WithDownstreamReRoute(downstreamReRoute)
|
.WithDownstreamReRoute(downstreamReRoute)
|
||||||
|
@ -20,7 +20,9 @@
|
|||||||
|
|
||||||
public IDownstreamRouteProvider Get(IInternalConfiguration config)
|
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");
|
_logger.LogInformation($"Selected {nameof(DownstreamRouteCreator)} as DownstreamRouteProvider for this request");
|
||||||
return _providers[nameof(DownstreamRouteCreator)];
|
return _providers[nameof(DownstreamRouteCreator)];
|
||||||
|
@ -52,6 +52,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
|
|||||||
}
|
}
|
||||||
|
|
||||||
var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value));
|
var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value));
|
||||||
|
|
||||||
Logger.LogDebug($"downstream templates are {downstreamPathTemplates}");
|
Logger.LogDebug($"downstream templates are {downstreamPathTemplates}");
|
||||||
|
|
||||||
context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues;
|
context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues;
|
||||||
|
@ -42,7 +42,6 @@
|
|||||||
UpstreamPathTemplate = "/api/ClientRateLimit",
|
UpstreamPathTemplate = "/api/ClientRateLimit",
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
RequestIdKey = _steps.RequestIdKey,
|
RequestIdKey = _steps.RequestIdKey,
|
||||||
|
|
||||||
RateLimitOptions = new FileRateLimitRule()
|
RateLimitOptions = new FileRateLimitRule()
|
||||||
{
|
{
|
||||||
EnableRateLimiting = true,
|
EnableRateLimiting = true,
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Consul;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
@ -22,9 +23,11 @@ namespace Ocelot.AcceptanceTests
|
|||||||
private readonly Steps _steps;
|
private readonly Steps _steps;
|
||||||
private IWebHost _fakeConsulBuilder;
|
private IWebHost _fakeConsulBuilder;
|
||||||
private FileConfiguration _config;
|
private FileConfiguration _config;
|
||||||
|
private List<ServiceEntry> _consulServices;
|
||||||
|
|
||||||
public ConfigurationInConsulTests()
|
public ConfigurationInConsulTests()
|
||||||
{
|
{
|
||||||
|
_consulServices = new List<ServiceEntry>();
|
||||||
_steps = new Steps();
|
_steps = new Steps();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +66,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
|
|
||||||
var fakeConsulServiceDiscoveryUrl = "http://localhost:9500";
|
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 => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
|
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
|
||||||
@ -108,7 +111,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
|
|
||||||
var fakeConsulServiceDiscoveryUrl = "http://localhost:9502";
|
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 => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache())
|
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache())
|
||||||
@ -168,7 +171,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => GivenTheConsulConfigurationIs(consulConfig))
|
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 => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
|
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
|
||||||
@ -257,7 +260,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => GivenTheConsulConfigurationIs(consulConfig))
|
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 => x.GivenThereIsAServiceRunningOn("http://localhost:51780", "/status", 200, "Hello from Laura"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
|
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
|
||||||
@ -269,6 +272,89 @@ namespace Ocelot.AcceptanceTests
|
|||||||
.BDDfy();
|
.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<FileDynamicReRoute>
|
||||||
|
{
|
||||||
|
new FileDynamicReRoute
|
||||||
|
{
|
||||||
|
ServiceName = serviceName,
|
||||||
|
RateLimitRule = new FileRateLimitRule()
|
||||||
|
{
|
||||||
|
EnableRateLimiting = true,
|
||||||
|
ClientWhitelist = new List<string>(),
|
||||||
|
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()
|
private void ThenTheConfigIsUpdatedInOcelot()
|
||||||
{
|
{
|
||||||
var result = WaitFor(20000).Until(() => {
|
var result = WaitFor(20000).Until(() => {
|
||||||
@ -292,7 +378,15 @@ namespace Ocelot.AcceptanceTests
|
|||||||
_config = 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()
|
_fakeConsulBuilder = new WebHostBuilder()
|
||||||
.UseUrls(url)
|
.UseUrls(url)
|
||||||
@ -336,6 +430,10 @@ namespace Ocelot.AcceptanceTests
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (context.Request.Path.Value == $"/v1/health/service/{serviceName}")
|
||||||
|
{
|
||||||
|
await context.Response.WriteJsonAsync(_consulServices);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
using Ocelot.Errors;
|
using Ocelot.Errors;
|
||||||
using Ocelot.UnitTests.TestData;
|
using Ocelot.UnitTests.TestData;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
|
using System;
|
||||||
|
|
||||||
public class FileInternalConfigurationCreatorTests
|
public class FileInternalConfigurationCreatorTests
|
||||||
{
|
{
|
||||||
@ -821,6 +822,54 @@
|
|||||||
.BDDfy();
|
.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<FileDynamicReRoute>
|
||||||
|
{
|
||||||
|
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<FileRateLimitRule>(), It.IsAny<FileGlobalConfiguration>()))
|
||||||
|
.Returns(rateLimitOptions);
|
||||||
|
}
|
||||||
|
|
||||||
private void GivenTheConfigIsInvalid(List<Error> errors)
|
private void GivenTheConfigIsInvalid(List<Error> errors)
|
||||||
{
|
{
|
||||||
_validator
|
_validator
|
||||||
@ -844,7 +893,7 @@
|
|||||||
private void ThenTheRateLimitOptionsCreatorIsCalledCorrectly()
|
private void ThenTheRateLimitOptionsCreatorIsCalledCorrectly()
|
||||||
{
|
{
|
||||||
_rateLimitOptions
|
_rateLimitOptions
|
||||||
.Verify(x => x.Create(It.IsAny<FileReRoute>(), It.IsAny<FileGlobalConfiguration>(), It.IsAny<bool>()), Times.Once);
|
.Verify(x => x.Create(It.IsAny<FileRateLimitRule>(), It.IsAny<FileGlobalConfiguration>()), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenTheConfigIsValid()
|
private void GivenTheConfigIsValid()
|
||||||
@ -864,6 +913,16 @@
|
|||||||
_config = _internalConfigurationCreator.Create(_fileConfiguration).Result;
|
_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<ReRoute> expectedReRoutes)
|
private void ThenTheReRoutesAre(List<ReRoute> expectedReRoutes)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _config.Data.ReRoutes.Count; i++)
|
for (int i = 0; i < _config.Data.ReRoutes.Count; i++)
|
||||||
|
@ -88,7 +88,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
|
|
||||||
private void WhenICreate()
|
private void WhenICreate()
|
||||||
{
|
{
|
||||||
_result = _creator.Create(_fileReRoute, _fileGlobalConfig, _enabled);
|
_result = _creator.Create(_fileReRoute.RateLimitOptions, _fileGlobalConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheFollowingIsReturned(RateLimitOptions expected)
|
private void ThenTheFollowingIsReturned(RateLimitOptions expected)
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
using Ocelot.DownstreamRouteFinder.Finder;
|
|
||||||
using Xunit;
|
|
||||||
using Shouldly;
|
|
||||||
using Ocelot.Configuration;
|
|
||||||
using System.Net.Http;
|
|
||||||
|
|
||||||
namespace Ocelot.UnitTests.DownstreamRouteFinder
|
namespace Ocelot.UnitTests.DownstreamRouteFinder
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
@ -14,6 +8,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
|||||||
using Ocelot.LoadBalancer.LoadBalancers;
|
using Ocelot.LoadBalancer.LoadBalancers;
|
||||||
using Responses;
|
using Responses;
|
||||||
using TestStack.BDDfy;
|
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
|
public class DownstreamRouteCreatorTests
|
||||||
{
|
{
|
||||||
@ -53,6 +53,34 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
|||||||
.BDDfy();
|
.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> { 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]
|
[Fact]
|
||||||
public void should_cache_downstream_route()
|
public void should_cache_downstream_route()
|
||||||
{
|
{
|
||||||
@ -174,6 +202,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
|||||||
.Returns(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()
|
private void ThenTheDownstreamRouteIsCreated()
|
||||||
{
|
{
|
||||||
_result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test");
|
_result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test");
|
||||||
|
@ -53,6 +53,21 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
|||||||
.BDDfy();
|
.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<ReRoute>
|
||||||
|
{
|
||||||
|
new ReRouteBuilder().WithUpstreamPathTemplate("woot").Build()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(_ => GivenTheReRoutes(reRoutes, spConfig))
|
||||||
|
.When(_ => WhenIGet())
|
||||||
|
.Then(_ => ThenTheResultShouldBe<Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteFinder>())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_return_downstream_route_finder_as_no_service_discovery_given_no_host()
|
public void should_return_downstream_route_finder_as_no_service_discovery_given_no_host()
|
||||||
{
|
{
|
||||||
@ -101,6 +116,21 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
|||||||
.BDDfy();
|
.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<ReRoute>
|
||||||
|
{
|
||||||
|
new ReRouteBuilder().Build()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(_ => GivenTheReRoutes(reRoutes, spConfig))
|
||||||
|
.When(_ => WhenIGet())
|
||||||
|
.Then(_ => ThenTheResultShouldBe<Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteCreator>())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
private void ThenTheResultShouldBe<T>()
|
private void ThenTheResultShouldBe<T>()
|
||||||
{
|
{
|
||||||
_result.ShouldBeOfType<T>();
|
_result.ShouldBeOfType<T>();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user