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:
Tom Pallister
2018-07-29 20:32:45 +01:00
committed by GitHub
parent 0f2cf2d188
commit b0a20d13b9
19 changed files with 652 additions and 350 deletions

View File

@ -103,6 +103,12 @@ namespace Ocelot.Configuration.Creator
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 @@ namespace Ocelot.Configuration.Creator
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
.SelectMany(x => x.DownstreamReRoute)
@ -186,7 +209,7 @@ namespace Ocelot.Configuration.Creator
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);

View File

@ -1,9 +1,9 @@
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public interface IRateLimitOptionsCreator
{
RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting);
}
}
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public interface IRateLimitOptionsCreator
{
RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration);
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -1,20 +1,22 @@
using System.Collections.Generic;
namespace Ocelot.Configuration.File
{
public class FileConfiguration
{
public FileConfiguration()
{
ReRoutes = new List<FileReRoute>();
GlobalConfiguration = new FileGlobalConfiguration();
Aggregates = new List<FileAggregateReRoute>();
}
public List<FileReRoute> ReRoutes { get; set; }
// Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates
public List<FileAggregateReRoute> Aggregates { get;set; }
public FileGlobalConfiguration GlobalConfiguration { get; set; }
}
}
using System.Collections.Generic;
namespace Ocelot.Configuration.File
{
public class FileConfiguration
{
public FileConfiguration()
{
ReRoutes = new List<FileReRoute>();
GlobalConfiguration = new FileGlobalConfiguration();
Aggregates = new List<FileAggregateReRoute>();
DynamicReRoutes = new List<FileDynamicReRoute>();
}
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
public List<FileAggregateReRoute> Aggregates { get;set; }
public FileGlobalConfiguration GlobalConfiguration { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace Ocelot.Configuration.File
{
public class FileDynamicReRoute
{
public string ServiceName { get; set; }
public FileRateLimitRule RateLimitRule { get; set; }
}
}

View File

@ -1,37 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ocelot.Configuration.File
{
public class FileRateLimitOptions
{
/// <summary>
/// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId
/// </summary>
public string ClientIdHeader { get; set; } = "ClientId";
/// <summary>
/// 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}
/// </summary>
public string QuotaExceededMessage { get; set; }
/// <summary>
/// Gets or sets the counter prefix, used to compose the rate limit counter cache key
/// </summary>
public string RateLimitCounterPrefix { get; set; } = "ocelot";
/// <summary>
/// Disables X-Rate-Limit and Rety-After headers
/// </summary>
public bool DisableRateLimitHeaders { get; set; }
/// <summary>
/// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests)
/// </summary>
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
{
/// <summary>
/// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId
/// </summary>
public string ClientIdHeader { get; set; } = "ClientId";
/// <summary>
/// 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}
/// </summary>
public string QuotaExceededMessage { get; set; }
/// <summary>
/// Gets or sets the counter prefix, used to compose the rate limit counter cache key
/// </summary>
public string RateLimitCounterPrefix { get; set; } = "ocelot";
/// <summary>
/// Disables X-Rate-Limit and Rety-After headers
/// </summary>
public bool DisableRateLimitHeaders { get; set; }
/// <summary>
/// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests)
/// </summary>
public int HttpStatusCode { get; set; } = 429;
}
}

View File

@ -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<string>();
}
public List<string> ClientWhitelist { get; set; }
/// <summary>
/// Enables endpoint rate limiting based URL path and HTTP verb
/// </summary>
public bool EnableRateLimiting { get; set; }
/// <summary>
/// Rate limit period as in 1s, 1m, 1h
/// </summary>
public string Period { get; set; }
public double PeriodTimespan { get; set; }
/// <summary>
/// Maximum number of requests that a client can make in a defined period
/// </summary>
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<string>();
}
public List<string> ClientWhitelist { get; set; }
/// <summary>
/// Enables endpoint rate limiting based URL path and HTTP verb
/// </summary>
public bool EnableRateLimiting { get; set; }
/// <summary>
/// Rate limit period as in 1s, 1m, 1h
/// </summary>
public string Period { get; set; }
public double PeriodTimespan { get; set; }
/// <summary>
/// Maximum number of requests that a client can make in a defined period
/// </summary>
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();
}
}
}

View File

@ -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; }
}
}

View File

@ -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 @@
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 @@
.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)

View File

@ -20,7 +20,9 @@
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)];

View File

@ -52,6 +52,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware
}
var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value));
Logger.LogDebug($"downstream templates are {downstreamPathTemplates}");
context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues;