1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-08-21 13:23:07 +00:00

Added support for ratelimiting key suffix, allowing parameter based ratelimiting

This commit is contained in:
JKorf
2025-02-17 17:26:04 +01:00
parent cd78dbf575
commit 3b15c35a02
9 changed files with 63 additions and 51 deletions
@@ -90,7 +90,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
}
/// <inheritdoc />
public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight)
public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix)
{
foreach(var filter in _filters)
{
@@ -101,7 +101,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
if (type == RateLimitItemType.Connection)
requestWeight = _connectionWeight ?? requestWeight;
var key = _keySelector(definition, host, apiKey);
var key = _keySelector(definition, host, apiKey) + keySuffix;
if (!_trackers.TryGetValue(key, out var tracker))
{
tracker = CreateTracker();
@@ -116,7 +116,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
}
/// <inheritdoc />
public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight)
public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix)
{
foreach (var filter in _filters)
{
@@ -127,7 +127,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
if (type == RateLimitItemType.Connection)
requestWeight = _connectionWeight ?? requestWeight;
var key = _keySelector(definition, host, apiKey);
var key = _keySelector(definition, host, apiKey) + keySuffix;
var tracker = _trackers[key];
tracker.ApplyWeight(requestWeight);
return RateLimitState.Applied(Limit, TimeSpan, tracker.Current);
@@ -42,7 +42,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
}
/// <inheritdoc />
public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight)
public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix)
{
if (type != Type)
return LimitCheck.NotApplicable;
@@ -55,7 +55,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
}
/// <inheritdoc />
public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight)
public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix)
{
return RateLimitState.NotApplied;
}
@@ -19,7 +19,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
/// <summary>
/// Endpoint limit per API key
/// </summary>
public static Func<RequestDefinition, string, string?, string> PerApiKey { get; } = new Func<RequestDefinition, string, string?, string>((def, host, key) => def.Path + def.Method);
public static Func<RequestDefinition, string, string?, string> PerApiKey { get; } = new Func<RequestDefinition, string, string?, string>((def, host, key) => def.Path + def.Method + key);
private readonly Dictionary<string, IWindowTracker> _trackers;
private readonly RateLimitWindowType _windowType;
@@ -53,9 +53,9 @@ namespace CryptoExchange.Net.RateLimiting.Guards
}
/// <inheritdoc />
public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight)
public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix)
{
var key = _keySelector(definition, host, apiKey);
var key = _keySelector(definition, host, apiKey) + keySuffix;
if (!_trackers.TryGetValue(key, out var tracker))
{
tracker = CreateTracker();
@@ -70,9 +70,9 @@ namespace CryptoExchange.Net.RateLimiting.Guards
}
/// <inheritdoc />
public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight)
public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix)
{
var key = _keySelector(definition, host, apiKey);
var key = _keySelector(definition, host, apiKey) + keySuffix;
var tracker = _trackers[key];
tracker.ApplyWeight(requestWeight);
return RateLimitState.Applied(_limit, _period, tracker.Current);
@@ -53,9 +53,10 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces
/// <param name="apiKey">The API key</param>
/// <param name="requestWeight">Request weight</param>
/// <param name="behaviour">Behaviour when rate limit is hit</param>
/// <param name="keySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param>
/// <param name="ct">Cancelation token</param>
/// <returns>Error if RateLimitingBehaviour is Fail and rate limit is hit</returns>
Task<CallResult> ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, int requestWeight, RateLimitingBehaviour behaviour, CancellationToken ct);
Task<CallResult> ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, int requestWeight, RateLimitingBehaviour behaviour, string? keySuffix, CancellationToken ct);
/// <summary>
/// Enforces the rate limit as defined in the request definition. When a rate limit is hit will wait for the rate limit to pass if RateLimitingBehaviour is Wait, or return an error if it is set to Fail
@@ -69,8 +70,9 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces
/// <param name="apiKey">The API key</param>
/// <param name="behaviour">Behaviour when rate limit is hit</param>
/// <param name="requestWeight">The weight to apply to the limit guard</param>
/// <param name="keySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param>
/// <param name="ct">Cancelation token</param>
/// <returns>Error if RateLimitingBehaviour is Fail and rate limit is hit</returns>
Task<CallResult> ProcessSingleAsync(ILogger logger, int itemId, IRateLimitGuard guard, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, int requestWeight, RateLimitingBehaviour behaviour, CancellationToken ct);
Task<CallResult> ProcessSingleAsync(ILogger logger, int itemId, IRateLimitGuard guard, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, int requestWeight, RateLimitingBehaviour behaviour, string? keySuffix, CancellationToken ct);
}
}
@@ -25,8 +25,9 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces
/// <param name="host">The host address</param>
/// <param name="apiKey">The API key</param>
/// <param name="requestWeight">The request weight</param>
/// <param name="keySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param>
/// <returns></returns>
LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight);
LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix);
/// <summary>
/// Apply the request to this guard with the specified weight
@@ -36,7 +37,8 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces
/// <param name="host">The host address</param>
/// <param name="apiKey">The API key</param>
/// <param name="requestWeight">The request weight</param>
/// <param name="keySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param>
/// <returns></returns>
RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight);
RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix);
}
}
@@ -37,14 +37,14 @@ namespace CryptoExchange.Net.RateLimiting
}
/// <inheritdoc />
public async Task<CallResult> ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, CancellationToken ct)
public async Task<CallResult> ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, string? keySuffix, CancellationToken ct)
{
await _semaphore.WaitAsync(ct).ConfigureAwait(false);
bool release = true;
_waitingCount++;
try
{
return await CheckGuardsAsync(_guards, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, ct).ConfigureAwait(false);
return await CheckGuardsAsync(_guards, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, keySuffix, ct).ConfigureAwait(false);
}
catch (TaskCanceledException)
{
@@ -71,6 +71,7 @@ namespace CryptoExchange.Net.RateLimiting
string? apiKey,
int requestWeight,
RateLimitingBehaviour rateLimitingBehaviour,
string? keySuffix,
CancellationToken ct)
{
await _semaphore.WaitAsync(ct).ConfigureAwait(false);
@@ -78,7 +79,7 @@ namespace CryptoExchange.Net.RateLimiting
_waitingCount++;
try
{
return await CheckGuardsAsync(new IRateLimitGuard[] { guard }, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, ct).ConfigureAwait(false);
return await CheckGuardsAsync(new IRateLimitGuard[] { guard }, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, keySuffix, ct).ConfigureAwait(false);
}
catch (TaskCanceledException)
{
@@ -94,12 +95,12 @@ namespace CryptoExchange.Net.RateLimiting
}
}
private async Task<CallResult> CheckGuardsAsync(IEnumerable<IRateLimitGuard> guards, ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, CancellationToken ct)
private async Task<CallResult> CheckGuardsAsync(IEnumerable<IRateLimitGuard> guards, ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, string? keySuffix, CancellationToken ct)
{
foreach (var guard in guards)
{
// Check if a wait is needed for this guard
var result = guard.Check(type, definition, host, apiKey, requestWeight);
var result = guard.Check(type, definition, host, apiKey, requestWeight, keySuffix);
if (result.Delay != TimeSpan.Zero && rateLimitingBehaviour == RateLimitingBehaviour.Fail)
{
// Delay is needed and limit behaviour is to fail the request
@@ -126,14 +127,14 @@ namespace CryptoExchange.Net.RateLimiting
RateLimitTriggered?.Invoke(new RateLimitEvent(itemId, _name, guard.Description, definition, host, result.Current, requestWeight, result.Limit, result.Period, result.Delay, rateLimitingBehaviour));
await Task.Delay((int)result.Delay.TotalMilliseconds + 1, ct).ConfigureAwait(false);
await _semaphore.WaitAsync(ct).ConfigureAwait(false);
return await CheckGuardsAsync(guards, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, ct).ConfigureAwait(false);
return await CheckGuardsAsync(guards, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, keySuffix, ct).ConfigureAwait(false);
}
}
// Apply the weight on each guard
foreach (var guard in guards)
{
var result = guard.ApplyWeight(type, definition, host, apiKey, requestWeight);
var result = guard.ApplyWeight(type, definition, host, apiKey, requestWeight, keySuffix);
if (result.IsApplied)
{
RateLimitUpdated?.Invoke(new RateLimitUpdateEvent(itemId, _name, guard.Description, result.Current, result.Limit, result.Period));