Skip to content

Commit

Permalink
Feature/more dynamic routes (ThreeMammals#508)
Browse files Browse the repository at this point in the history
* 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 ThreeMammals#458

* removed comments

* ThreeMammals#458 allow users to set rate limits per service for dynamic re routes

* ThreeMammals#458 added docs for rate limting on dynamic reroutes
  • Loading branch information
TomPallister authored Jul 29, 2018
1 parent 0f2cf2d commit b0a20d1
Show file tree
Hide file tree
Showing 19 changed files with 652 additions and 350 deletions.
50 changes: 40 additions & 10 deletions docs/features/servicediscovery.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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 segmentof the upstream path to lookup the
downstream service with the service discovery provider.
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.

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

Expand Down Expand Up @@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ private async Task<Response<IInternalConfiguration>> 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);
Expand All @@ -124,7 +130,24 @@ private async Task<Response<IInternalConfiguration>> SetUpConfiguration(FileConf
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)
Expand Down Expand Up @@ -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);

Expand Down
18 changes: 9 additions & 9 deletions src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
64 changes: 32 additions & 32 deletions src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
88 changes: 44 additions & 44 deletions src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
42 changes: 22 additions & 20 deletions src/Ocelot/Configuration/File/FileConfiguration.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}
8 changes: 8 additions & 0 deletions src/Ocelot/Configuration/File/FileDynamicReRoute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Ocelot.Configuration.File
{
public class FileDynamicReRoute
{
public string ServiceName { get; set; }
public FileRateLimitRule RateLimitRule { get; set; }
}
}
Loading

0 comments on commit b0a20d1

Please sign in to comment.