Merge branch 'feature/dynamic-rate-limit-client-whitelist' of https://github.com/totubamowo/Ocelot into totubamowo-feature/dynamic-rate-limit-client-whitelist

This commit is contained in:
TomPallister 2020-01-19 17:33:50 +00:00
commit 02c9711369
5 changed files with 45 additions and 37 deletions

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
namespace Ocelot.Configuration.Builder namespace Ocelot.Configuration.Builder
{ {
@ -7,6 +8,7 @@ namespace Ocelot.Configuration.Builder
private bool _enableRateLimiting; private bool _enableRateLimiting;
private string _clientIdHeader; private string _clientIdHeader;
private List<string> _clientWhitelist; private List<string> _clientWhitelist;
private Func<List<string>> _getClientWhitelist;
private bool _disableRateLimitHeaders; private bool _disableRateLimitHeaders;
private string _quotaExceededMessage; private string _quotaExceededMessage;
private string _rateLimitCounterPrefix; private string _rateLimitCounterPrefix;
@ -19,15 +21,15 @@ namespace Ocelot.Configuration.Builder
return this; return this;
} }
public RateLimitOptionsBuilder WithClientIdHeader(string clientIdheader) public RateLimitOptionsBuilder WithClientIdHeader(string clientIdHeader)
{ {
_clientIdHeader = clientIdheader; _clientIdHeader = clientIdHeader;
return this; return this;
} }
public RateLimitOptionsBuilder WithClientWhiteList(List<string> clientWhitelist) public RateLimitOptionsBuilder WithClientWhiteList(Func<List<string>> getClientWhitelist)
{ {
_clientWhitelist = clientWhitelist; _getClientWhitelist = getClientWhitelist;
return this; return this;
} }
@ -63,9 +65,9 @@ namespace Ocelot.Configuration.Builder
public RateLimitOptions Build() public RateLimitOptions Build()
{ {
return new RateLimitOptions(_enableRateLimiting, _clientIdHeader, _clientWhitelist, return new RateLimitOptions(_enableRateLimiting, _clientIdHeader, _getClientWhitelist,
_disableRateLimitHeaders, _quotaExceededMessage, _rateLimitCounterPrefix, _disableRateLimitHeaders, _quotaExceededMessage, _rateLimitCounterPrefix,
_rateLimitRule, _httpStatusCode); _rateLimitRule, _httpStatusCode);
} }
} }
} }

View File

@ -11,7 +11,7 @@ namespace Ocelot.Configuration.Creator
{ {
return new RateLimitOptionsBuilder() return new RateLimitOptionsBuilder()
.WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader) .WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader)
.WithClientWhiteList(fileRateLimitRule.ClientWhitelist) .WithClientWhiteList(() => fileRateLimitRule.ClientWhitelist)
.WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders) .WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders)
.WithEnableRateLimiting(fileRateLimitRule.EnableRateLimiting) .WithEnableRateLimiting(fileRateLimitRule.EnableRateLimiting)
.WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode) .WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode)

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
namespace Ocelot.Configuration namespace Ocelot.Configuration
{ {
@ -7,12 +8,14 @@ namespace Ocelot.Configuration
/// </summary> /// </summary>
public class RateLimitOptions public class RateLimitOptions
{ {
public RateLimitOptions(bool enbleRateLimiting, string clientIdHeader, List<string> clientWhitelist, bool disableRateLimitHeaders, private readonly Func<List<string>> _getClientWhitelist;
public RateLimitOptions(bool enableRateLimiting, string clientIdHeader, Func<List<string>> getClientWhitelist, bool disableRateLimitHeaders,
string quotaExceededMessage, string rateLimitCounterPrefix, RateLimitRule rateLimitRule, int httpStatusCode) string quotaExceededMessage, string rateLimitCounterPrefix, RateLimitRule rateLimitRule, int httpStatusCode)
{ {
EnableRateLimiting = enbleRateLimiting; EnableRateLimiting = enableRateLimiting;
ClientIdHeader = clientIdHeader; ClientIdHeader = clientIdHeader;
ClientWhitelist = clientWhitelist ?? new List<string>(); _getClientWhitelist = getClientWhitelist;
DisableRateLimitHeaders = disableRateLimitHeaders; DisableRateLimitHeaders = disableRateLimitHeaders;
QuotaExceededMessage = quotaExceededMessage; QuotaExceededMessage = quotaExceededMessage;
RateLimitCounterPrefix = rateLimitCounterPrefix; RateLimitCounterPrefix = rateLimitCounterPrefix;
@ -22,18 +25,21 @@ namespace Ocelot.Configuration
public RateLimitRule RateLimitRule { get; private set; } public RateLimitRule RateLimitRule { get; private set; }
public List<string> ClientWhitelist { get; private set; } /// <summary>
/// Gets the list of white listed clients
/// </summary>
public List<string> ClientWhitelist { get => _getClientWhitelist(); }
/// <summary> /// <summary>
/// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId
/// </summary> /// </summary>
public string ClientIdHeader { get; private set; } public string ClientIdHeader { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests)
/// </summary> /// </summary>
public int HttpStatusCode { get; private set; } public int HttpStatusCode { get; private set; }
/// <summary> /// <summary>
/// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message.
/// If none specified the default will be: /// If none specified the default will be:
@ -44,8 +50,8 @@ namespace Ocelot.Configuration
/// <summary> /// <summary>
/// Gets or sets the counter prefix, used to compose the rate limit counter cache key /// Gets or sets the counter prefix, used to compose the rate limit counter cache key
/// </summary> /// </summary>
public string RateLimitCounterPrefix { get; private set; } public string RateLimitCounterPrefix { get; private set; }
/// <summary> /// <summary>
/// Enables endpoint rate limiting based URL path and HTTP verb /// Enables endpoint rate limiting based URL path and HTTP verb
/// </summary> /// </summary>
@ -56,4 +62,4 @@ namespace Ocelot.Configuration
/// </summary> /// </summary>
public bool DisableRateLimitHeaders { get; private set; } public bool DisableRateLimitHeaders { get; private set; }
} }
} }

View File

@ -50,7 +50,7 @@ namespace Ocelot.UnitTests.Configuration
}; };
var expected = new RateLimitOptionsBuilder() var expected = new RateLimitOptionsBuilder()
.WithClientIdHeader("ClientIdHeader") .WithClientIdHeader("ClientIdHeader")
.WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) .WithClientWhiteList(() => fileReRoute.RateLimitOptions.ClientWhitelist)
.WithDisableRateLimitHeaders(true) .WithDisableRateLimitHeaders(true)
.WithEnableRateLimiting(true) .WithEnableRateLimiting(true)
.WithHttpStatusCode(200) .WithHttpStatusCode(200)

View File

@ -49,15 +49,15 @@ namespace Ocelot.UnitTests.RateLimit
[Fact] [Fact]
public void should_call_middleware_and_ratelimiting() public void should_call_middleware_and_ratelimiting()
{ {
var upstreamTemplate = new UpstreamPathTemplateBuilder().Build(); var upstreamTemplate = new UpstreamPathTemplateBuilder().Build();
var downstreamReRoute = new DownstreamReRouteBuilder() var downstreamReRoute = new DownstreamReRouteBuilder()
.WithEnableRateLimiting(true) .WithEnableRateLimiting(true)
.WithRateLimitOptions(new RateLimitOptions(true, "ClientId", new List<string>(), false, "", "", new RateLimitRule("1s", 100, 3), 429)) .WithRateLimitOptions(new RateLimitOptions(true, "ClientId", () => new List<string>(), false, "", "", new RateLimitRule("1s", 100, 3), 429))
.WithUpstreamHttpMethod(new List<string> { "Get" }) .WithUpstreamHttpMethod(new List<string> { "Get" })
.WithUpstreamPathTemplate(upstreamTemplate) .WithUpstreamPathTemplate(upstreamTemplate)
.Build(); .Build();
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()
.WithDownstreamReRoute(downstreamReRoute) .WithDownstreamReRoute(downstreamReRoute)
@ -82,7 +82,7 @@ namespace Ocelot.UnitTests.RateLimit
.WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder()
.WithEnableRateLimiting(true) .WithEnableRateLimiting(true)
.WithRateLimitOptions( .WithRateLimitOptions(
new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List<string>() { "ocelotclient2" }, false, "", "", new RateLimitRule("1s", 100, 3), 429)) new Ocelot.Configuration.RateLimitOptions(true, "ClientId", () => new List<string>() { "ocelotclient2" }, false, "", "", new RateLimitRule("1s", 100, 3), 429))
.WithUpstreamHttpMethod(new List<string> { "Get" }) .WithUpstreamHttpMethod(new List<string> { "Get" })
.Build()) .Build())
.WithUpstreamHttpMethod(new List<string> { "Get" }) .WithUpstreamHttpMethod(new List<string> { "Get" })
@ -102,8 +102,8 @@ namespace Ocelot.UnitTests.RateLimit
private void WhenICallTheMiddlewareMultipleTime(int times) private void WhenICallTheMiddlewareMultipleTime(int times)
{ {
var clientId = "ocelotclient1"; var clientId = "ocelotclient1";
for (int i = 0; i < times; i++) for (int i = 0; i < times; i++)
{ {
var request = new HttpRequestMessage(new HttpMethod("GET"), _url); var request = new HttpRequestMessage(new HttpMethod("GET"), _url);
@ -117,8 +117,8 @@ namespace Ocelot.UnitTests.RateLimit
private void WhenICallTheMiddlewareWithWhiteClient() private void WhenICallTheMiddlewareWithWhiteClient()
{ {
var clientId = "ocelotclient2"; var clientId = "ocelotclient2";
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {
var request = new HttpRequestMessage(new HttpMethod("GET"), _url); var request = new HttpRequestMessage(new HttpMethod("GET"), _url);
@ -127,10 +127,10 @@ namespace Ocelot.UnitTests.RateLimit
_downstreamContext.HttpContext.Request.Headers.TryAdd("ClientId", clientId); _downstreamContext.HttpContext.Request.Headers.TryAdd("ClientId", clientId);
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
_responseStatusCode = (int)_downstreamContext.HttpContext.Response.StatusCode; _responseStatusCode = (int)_downstreamContext.HttpContext.Response.StatusCode;
} }
} }
private void ThenresponseStatusCodeIs429() private void ThenresponseStatusCodeIs429()
{ {
_responseStatusCode.ShouldBe(429); _responseStatusCode.ShouldBe(429);
@ -145,7 +145,7 @@ namespace Ocelot.UnitTests.RateLimit
internal class FakeStream : Stream internal class FakeStream : Stream
{ {
public override void Flush() public override void Flush()
{ {
//do nothing //do nothing
//throw new System.NotImplementedException(); //throw new System.NotImplementedException();
} }
@ -176,4 +176,4 @@ namespace Ocelot.UnitTests.RateLimit
public override long Length { get; } public override long Length { get; }
public override long Position { get; set; } public override long Position { get; set; }
} }
} }