mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 02:42:52 +08:00
This commit is contained in:
parent
87c13bd9b4
commit
9979f8a4b8
@ -1,40 +1,40 @@
|
|||||||
Rate Limiting
|
Rate Limiting
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Thanks to `@catcherwong article <http://www.c-sharpcorner.com/article/building-api-gateway-using-ocelot-in-asp-net-core-rate-limiting-part-four/>`_ for inspiring me to finally write this documentation.
|
Thanks to `@catcherwong article <http://www.c-sharpcorner.com/article/building-api-gateway-using-ocelot-in-asp-net-core-rate-limiting-part-four/>`_ for inspiring me to finally write this documentation.
|
||||||
|
|
||||||
Ocelot supports rate limiting of upstream requests so that your downstream services do not become overloaded. This feature was added by @geffzhang on GitHub! Thanks very much.
|
Ocelot supports rate limiting of upstream requests so that your downstream services do not become overloaded. This feature was added by @geffzhang on GitHub! Thanks very much.
|
||||||
|
|
||||||
OK so to get rate limiting working for a ReRoute you need to add the following json to it.
|
OK so to get rate limiting working for a ReRoute you need to add the following json to it.
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
"RateLimitOptions": {
|
"RateLimitOptions": {
|
||||||
"ClientWhitelist": [],
|
"ClientWhitelist": [],
|
||||||
"EnableRateLimiting": true,
|
"EnableRateLimiting": true,
|
||||||
"Period": "1s",
|
"Period": "1s",
|
||||||
"PeriodTimespan": 1,
|
"PeriodTimespan": 1,
|
||||||
"Limit": 1
|
"Limit": 1
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientWhitelist - This is an array that contains the whitelist of the client. It means that the client in this array will not be affected by the rate limiting.
|
ClientWhitelist - This is an array that contains the whitelist of the client. It means that the client in this array will not be affected by the rate limiting.
|
||||||
EnableRateLimiting - This value specifies enable endpoint rate limiting.
|
EnableRateLimiting - This value specifies enable endpoint rate limiting.
|
||||||
Period - This value specifies the period, such as 1s, 5m, 1h,1d and so on.
|
Period - This value specifies the period that the limit applies to, such as 1s, 5m, 1h,1d and so on. If you make more requests in the period than the limit allows then you need to wait for PeriodTimespan to elapse before you make another request.
|
||||||
PeriodTimespan - This value specifies that we can retry after a certain number of seconds.
|
PeriodTimespan - This value specifies that we can retry after a certain number of seconds.
|
||||||
Limit - This value specifies the maximum number of requests that a client can make in a defined period.
|
Limit - This value specifies the maximum number of requests that a client can make in a defined period.
|
||||||
|
|
||||||
You can also set the following in the GlobalConfiguration part of ocelot.json
|
You can also set the following in the GlobalConfiguration part of ocelot.json
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
"RateLimitOptions": {
|
"RateLimitOptions": {
|
||||||
"DisableRateLimitHeaders": false,
|
"DisableRateLimitHeaders": false,
|
||||||
"QuotaExceededMessage": "Customize Tips!",
|
"QuotaExceededMessage": "Customize Tips!",
|
||||||
"HttpStatusCode": 999,
|
"HttpStatusCode": 999,
|
||||||
"ClientIdHeader" : "Test"
|
"ClientIdHeader" : "Test"
|
||||||
}
|
}
|
||||||
|
|
||||||
DisableRateLimitHeaders - This value specifies whether X-Rate-Limit and Rety-After headers are disabled.
|
DisableRateLimitHeaders - This value specifies whether X-Rate-Limit and Rety-After headers are disabled.
|
||||||
QuotaExceededMessage - This value specifies the exceeded message.
|
QuotaExceededMessage - This value specifies the exceeded message.
|
||||||
HttpStatusCode - This value specifies the returned HTTP Status code when rate limiting occurs.
|
HttpStatusCode - This value specifies the returned HTTP Status code when rate limiting occurs.
|
||||||
ClientIdHeader - Allows you to specifiy the header that should be used to identify clients. By default it is "ClientId"
|
ClientIdHeader - Allows you to specifiy the header that should be used to identify clients. By default it is "ClientId"
|
||||||
|
@ -1,148 +1,148 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ocelot.RateLimit
|
namespace Ocelot.RateLimit
|
||||||
{
|
{
|
||||||
public class RateLimitCore
|
public class RateLimitCore
|
||||||
{
|
{
|
||||||
private readonly IRateLimitCounterHandler _counterHandler;
|
private readonly IRateLimitCounterHandler _counterHandler;
|
||||||
private static readonly object _processLocker = new object();
|
private static readonly object _processLocker = new object();
|
||||||
|
|
||||||
public RateLimitCore(IRateLimitCounterHandler counterStore)
|
public RateLimitCore(IRateLimitCounterHandler counterStore)
|
||||||
{
|
{
|
||||||
_counterHandler = counterStore;
|
_counterHandler = counterStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitOptions option)
|
public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitOptions option)
|
||||||
{
|
{
|
||||||
RateLimitCounter counter = new RateLimitCounter(DateTime.UtcNow, 1);
|
RateLimitCounter counter = new RateLimitCounter(DateTime.UtcNow, 1);
|
||||||
var rule = option.RateLimitRule;
|
var rule = option.RateLimitRule;
|
||||||
|
|
||||||
var counterId = ComputeCounterKey(requestIdentity, option);
|
var counterId = ComputeCounterKey(requestIdentity, option);
|
||||||
|
|
||||||
// serial reads and writes
|
// serial reads and writes
|
||||||
lock (_processLocker)
|
lock (_processLocker)
|
||||||
{
|
{
|
||||||
var entry = _counterHandler.Get(counterId);
|
var entry = _counterHandler.Get(counterId);
|
||||||
if (entry.HasValue)
|
if (entry.HasValue)
|
||||||
{
|
{
|
||||||
// entry has not expired
|
// entry has not expired
|
||||||
if (entry.Value.Timestamp + ConvertToTimeSpan(rule.Period) >= DateTime.UtcNow)
|
if (entry.Value.Timestamp + TimeSpan.FromSeconds(rule.PeriodTimespan) >= DateTime.UtcNow)
|
||||||
{
|
{
|
||||||
// increment request count
|
// increment request count
|
||||||
var totalRequests = entry.Value.TotalRequests + 1;
|
var totalRequests = entry.Value.TotalRequests + 1;
|
||||||
|
|
||||||
// deep copy
|
// deep copy
|
||||||
counter = new RateLimitCounter(entry.Value.Timestamp, totalRequests);
|
counter = new RateLimitCounter(entry.Value.Timestamp, totalRequests);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (counter.TotalRequests > rule.Limit)
|
if (counter.TotalRequests > rule.Limit)
|
||||||
{
|
{
|
||||||
var retryAfter = RetryAfterFrom(counter.Timestamp, rule);
|
var retryAfter = RetryAfterFrom(counter.Timestamp, rule);
|
||||||
if (retryAfter > 0)
|
if (retryAfter > 0)
|
||||||
{
|
{
|
||||||
var expirationTime = TimeSpan.FromSeconds(rule.PeriodTimespan);
|
var expirationTime = TimeSpan.FromSeconds(rule.PeriodTimespan);
|
||||||
_counterHandler.Set(counterId, counter, expirationTime);
|
_counterHandler.Set(counterId, counter, expirationTime);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_counterHandler.Remove(counterId);
|
_counterHandler.Remove(counterId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var expirationTime = ConvertToTimeSpan(rule.Period);
|
var expirationTime = ConvertToTimeSpan(rule.Period);
|
||||||
_counterHandler.Set(counterId, counter, expirationTime);
|
_counterHandler.Set(counterId, counter, expirationTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
return counter;
|
return counter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveRateLimitCounter(ClientRequestIdentity requestIdentity, RateLimitOptions option, RateLimitCounter counter, TimeSpan expirationTime)
|
public void SaveRateLimitCounter(ClientRequestIdentity requestIdentity, RateLimitOptions option, RateLimitCounter counter, TimeSpan expirationTime)
|
||||||
{
|
{
|
||||||
var counterId = ComputeCounterKey(requestIdentity, option);
|
var counterId = ComputeCounterKey(requestIdentity, option);
|
||||||
var rule = option.RateLimitRule;
|
var rule = option.RateLimitRule;
|
||||||
|
|
||||||
// stores: id (string) - timestamp (datetime) - total_requests (long)
|
// stores: id (string) - timestamp (datetime) - total_requests (long)
|
||||||
_counterHandler.Set(counterId, counter, expirationTime);
|
_counterHandler.Set(counterId, counter, expirationTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RateLimitHeaders GetRateLimitHeaders(HttpContext context, ClientRequestIdentity requestIdentity, RateLimitOptions option)
|
public RateLimitHeaders GetRateLimitHeaders(HttpContext context, ClientRequestIdentity requestIdentity, RateLimitOptions option)
|
||||||
{
|
{
|
||||||
var rule = option.RateLimitRule;
|
var rule = option.RateLimitRule;
|
||||||
RateLimitHeaders headers = null;
|
RateLimitHeaders headers = null;
|
||||||
var counterId = ComputeCounterKey(requestIdentity, option);
|
var counterId = ComputeCounterKey(requestIdentity, option);
|
||||||
var entry = _counterHandler.Get(counterId);
|
var entry = _counterHandler.Get(counterId);
|
||||||
if (entry.HasValue)
|
if (entry.HasValue)
|
||||||
{
|
{
|
||||||
headers = new RateLimitHeaders(context, rule.Period,
|
headers = new RateLimitHeaders(context, rule.Period,
|
||||||
(rule.Limit - entry.Value.TotalRequests).ToString(),
|
(rule.Limit - entry.Value.TotalRequests).ToString(),
|
||||||
(entry.Value.Timestamp + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo)
|
(entry.Value.Timestamp + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
headers = new RateLimitHeaders(context,
|
headers = new RateLimitHeaders(context,
|
||||||
rule.Period,
|
rule.Period,
|
||||||
rule.Limit.ToString(),
|
rule.Limit.ToString(),
|
||||||
(DateTime.UtcNow + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo));
|
(DateTime.UtcNow + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ComputeCounterKey(ClientRequestIdentity requestIdentity, RateLimitOptions option)
|
public string ComputeCounterKey(ClientRequestIdentity requestIdentity, RateLimitOptions option)
|
||||||
{
|
{
|
||||||
var key = $"{option.RateLimitCounterPrefix}_{requestIdentity.ClientId}_{option.RateLimitRule.Period}_{requestIdentity.HttpVerb}_{requestIdentity.Path}";
|
var key = $"{option.RateLimitCounterPrefix}_{requestIdentity.ClientId}_{option.RateLimitRule.Period}_{requestIdentity.HttpVerb}_{requestIdentity.Path}";
|
||||||
|
|
||||||
var idBytes = Encoding.UTF8.GetBytes(key);
|
var idBytes = Encoding.UTF8.GetBytes(key);
|
||||||
|
|
||||||
byte[] hashBytes;
|
byte[] hashBytes;
|
||||||
|
|
||||||
using (var algorithm = SHA1.Create())
|
using (var algorithm = SHA1.Create())
|
||||||
{
|
{
|
||||||
hashBytes = algorithm.ComputeHash(idBytes);
|
hashBytes = algorithm.ComputeHash(idBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return BitConverter.ToString(hashBytes).Replace("-", string.Empty);
|
return BitConverter.ToString(hashBytes).Replace("-", string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int RetryAfterFrom(DateTime timestamp, RateLimitRule rule)
|
public int RetryAfterFrom(DateTime timestamp, RateLimitRule rule)
|
||||||
{
|
{
|
||||||
var secondsPast = Convert.ToInt32((DateTime.UtcNow - timestamp).TotalSeconds);
|
var secondsPast = Convert.ToInt32((DateTime.UtcNow - timestamp).TotalSeconds);
|
||||||
var retryAfter = Convert.ToInt32(TimeSpan.FromSeconds(rule.PeriodTimespan).TotalSeconds);
|
var retryAfter = Convert.ToInt32(TimeSpan.FromSeconds(rule.PeriodTimespan).TotalSeconds);
|
||||||
retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1;
|
retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1;
|
||||||
return retryAfter;
|
return retryAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeSpan ConvertToTimeSpan(string timeSpan)
|
public TimeSpan ConvertToTimeSpan(string timeSpan)
|
||||||
{
|
{
|
||||||
var l = timeSpan.Length - 1;
|
var l = timeSpan.Length - 1;
|
||||||
var value = timeSpan.Substring(0, l);
|
var value = timeSpan.Substring(0, l);
|
||||||
var type = timeSpan.Substring(l, 1);
|
var type = timeSpan.Substring(l, 1);
|
||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case "d":
|
case "d":
|
||||||
return TimeSpan.FromDays(double.Parse(value));
|
return TimeSpan.FromDays(double.Parse(value));
|
||||||
case "h":
|
case "h":
|
||||||
return TimeSpan.FromHours(double.Parse(value));
|
return TimeSpan.FromHours(double.Parse(value));
|
||||||
case "m":
|
case "m":
|
||||||
return TimeSpan.FromMinutes(double.Parse(value));
|
return TimeSpan.FromMinutes(double.Parse(value));
|
||||||
case "s":
|
case "s":
|
||||||
return TimeSpan.FromSeconds(double.Parse(value));
|
return TimeSpan.FromSeconds(double.Parse(value));
|
||||||
default:
|
default:
|
||||||
throw new FormatException($"{timeSpan} can't be converted to TimeSpan, unknown type {type}");
|
throw new FormatException($"{timeSpan} can't be converted to TimeSpan, unknown type {type}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,171 +1,236 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Ocelot.AcceptanceTests
|
namespace Ocelot.AcceptanceTests
|
||||||
{
|
{
|
||||||
public class ClientRateLimitTests : IDisposable
|
public class ClientRateLimitTests : IDisposable
|
||||||
{
|
{
|
||||||
private IWebHost _builder;
|
private IWebHost _builder;
|
||||||
private readonly Steps _steps;
|
private readonly Steps _steps;
|
||||||
private int _counterOne;
|
private int _counterOne;
|
||||||
|
|
||||||
public ClientRateLimitTests()
|
public ClientRateLimitTests()
|
||||||
{
|
{
|
||||||
_steps = new Steps();
|
_steps = new Steps();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_builder?.Dispose();
|
_builder?.Dispose();
|
||||||
_steps.Dispose();
|
_steps.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_call_withratelimiting()
|
public void should_call_withratelimiting()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/api/ClientRateLimit",
|
DownstreamPathTemplate = "/api/ClientRateLimit",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 51876,
|
Port = 51876,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
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,
|
||||||
ClientWhitelist = new List<string>(),
|
ClientWhitelist = new List<string>(),
|
||||||
Limit = 3,
|
Limit = 3,
|
||||||
Period = "1s",
|
Period = "1s",
|
||||||
PeriodTimespan = 1000
|
PeriodTimespan = 1000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
{
|
{
|
||||||
RateLimitOptions = new FileRateLimitOptions()
|
RateLimitOptions = new FileRateLimitOptions()
|
||||||
{
|
{
|
||||||
ClientIdHeader = "ClientId",
|
ClientIdHeader = "ClientId",
|
||||||
DisableRateLimitHeaders = false,
|
DisableRateLimitHeaders = false,
|
||||||
QuotaExceededMessage = "",
|
QuotaExceededMessage = "",
|
||||||
RateLimitCounterPrefix = "",
|
RateLimitCounterPrefix = "",
|
||||||
HttpStatusCode = 428
|
HttpStatusCode = 428
|
||||||
},
|
},
|
||||||
RequestIdKey ="oceclientrequest"
|
RequestIdKey ="oceclientrequest"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", "/api/ClientRateLimit"))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", "/api/ClientRateLimit"))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 2))
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 2))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(428))
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(428))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_call_middleware_withWhitelistClient()
|
public void should_wait_for_period_timespan_to_elapse_before_making_next_request()
|
||||||
{
|
{
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
ReRoutes = new List<FileReRoute>
|
ReRoutes = new List<FileReRoute>
|
||||||
{
|
{
|
||||||
new FileReRoute
|
new FileReRoute
|
||||||
{
|
{
|
||||||
DownstreamPathTemplate = "/api/ClientRateLimit",
|
DownstreamPathTemplate = "/api/ClientRateLimit",
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
{
|
{
|
||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 51876,
|
Port = 51926,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DownstreamScheme = "http",
|
DownstreamScheme = "http",
|
||||||
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,
|
||||||
ClientWhitelist = new List<string>() { "ocelotclient1"},
|
ClientWhitelist = new List<string>(),
|
||||||
Limit = 3,
|
Limit = 3,
|
||||||
Period = "1s",
|
Period = "1s",
|
||||||
PeriodTimespan = 100
|
PeriodTimespan = 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
{
|
{
|
||||||
RateLimitOptions = new FileRateLimitOptions()
|
RateLimitOptions = new FileRateLimitOptions()
|
||||||
{
|
{
|
||||||
ClientIdHeader = "ClientId",
|
ClientIdHeader = "ClientId",
|
||||||
DisableRateLimitHeaders = false,
|
DisableRateLimitHeaders = false,
|
||||||
QuotaExceededMessage = "",
|
QuotaExceededMessage = "",
|
||||||
RateLimitCounterPrefix = ""
|
RateLimitCounterPrefix = "",
|
||||||
},
|
HttpStatusCode = 428
|
||||||
RequestIdKey = "oceclientrequest"
|
},
|
||||||
}
|
RequestIdKey ="oceclientrequest"
|
||||||
};
|
}
|
||||||
|
};
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", "/api/ClientRateLimit"))
|
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51926", "/api/ClientRateLimit"))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 4))
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
|
||||||
.BDDfy();
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
||||||
}
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 2))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
||||||
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath)
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
|
||||||
{
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(428))
|
||||||
_builder = new WebHostBuilder()
|
.And(x => _steps.GivenIWait(1000))
|
||||||
.UseUrls(baseUrl)
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
|
||||||
.UseKestrel()
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(428))
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.And(x => _steps.GivenIWait(1000))
|
||||||
.UseIISIntegration()
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1))
|
||||||
.UseUrls(baseUrl)
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
||||||
.Configure(app =>
|
.BDDfy();
|
||||||
{
|
}
|
||||||
app.UsePathBase(basePath);
|
|
||||||
app.Run(context =>
|
[Fact]
|
||||||
{
|
public void should_call_middleware_withWhitelistClient()
|
||||||
_counterOne++;
|
{
|
||||||
context.Response.StatusCode = 200;
|
var configuration = new FileConfiguration
|
||||||
context.Response.WriteAsync(_counterOne.ToString());
|
{
|
||||||
return Task.CompletedTask;
|
ReRoutes = new List<FileReRoute>
|
||||||
});
|
{
|
||||||
})
|
new FileReRoute
|
||||||
.Build();
|
{
|
||||||
|
DownstreamPathTemplate = "/api/ClientRateLimit",
|
||||||
_builder.Start();
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
}
|
{
|
||||||
}
|
new FileHostAndPort
|
||||||
}
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = 51876,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
UpstreamPathTemplate = "/api/ClientRateLimit",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
RequestIdKey = _steps.RequestIdKey,
|
||||||
|
|
||||||
|
RateLimitOptions = new FileRateLimitRule()
|
||||||
|
{
|
||||||
|
EnableRateLimiting = true,
|
||||||
|
ClientWhitelist = new List<string>() { "ocelotclient1"},
|
||||||
|
Limit = 3,
|
||||||
|
Period = "1s",
|
||||||
|
PeriodTimespan = 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
|
{
|
||||||
|
RateLimitOptions = new FileRateLimitOptions()
|
||||||
|
{
|
||||||
|
ClientIdHeader = "ClientId",
|
||||||
|
DisableRateLimitHeaders = false,
|
||||||
|
QuotaExceededMessage = "",
|
||||||
|
RateLimitCounterPrefix = ""
|
||||||
|
},
|
||||||
|
RequestIdKey = "oceclientrequest"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51876", "/api/ClientRateLimit"))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 4))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath)
|
||||||
|
{
|
||||||
|
_builder = new WebHostBuilder()
|
||||||
|
.UseUrls(baseUrl)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.UseUrls(baseUrl)
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UsePathBase(basePath);
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
_counterOne++;
|
||||||
|
context.Response.StatusCode = 200;
|
||||||
|
context.Response.WriteAsync(_counterOne.ToString());
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builder.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -183,6 +183,11 @@ namespace Ocelot.AcceptanceTests
|
|||||||
_ocelotClient = _ocelotServer.CreateClient();
|
_ocelotClient = _ocelotServer.CreateClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void GivenIWait(int wait)
|
||||||
|
{
|
||||||
|
Thread.Sleep(wait);
|
||||||
|
}
|
||||||
|
|
||||||
public void GivenOcelotIsRunningWithMiddleareBeforePipeline<T>(Func<object, Task> callback)
|
public void GivenOcelotIsRunningWithMiddleareBeforePipeline<T>(Func<object, Task> callback)
|
||||||
{
|
{
|
||||||
_webHostBuilder = new WebHostBuilder();
|
_webHostBuilder = new WebHostBuilder();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user