feat(web): implement KisApiClient request throttling to prevent KIS OpenAPI rate limits
This commit is contained in:
@@ -32,6 +32,8 @@ public class KisApiClient : IKisApiClient
|
|||||||
private readonly ITokenCache _tokenCache;
|
private readonly ITokenCache _tokenCache;
|
||||||
private readonly ILogger<KisApiClient> _logger;
|
private readonly ILogger<KisApiClient> _logger;
|
||||||
private static readonly ConcurrentDictionary<string, SemaphoreSlim> TokenLocks = new();
|
private static readonly ConcurrentDictionary<string, SemaphoreSlim> TokenLocks = new();
|
||||||
|
private readonly SemaphoreSlim _rateLimitSemaphore = new(1, 1);
|
||||||
|
private DateTime _lastRequestTime = DateTime.MinValue;
|
||||||
|
|
||||||
public KisApiClient(HttpClient httpClient, ITokenCache tokenCache, ILogger<KisApiClient> logger)
|
public KisApiClient(HttpClient httpClient, ITokenCache tokenCache, ILogger<KisApiClient> logger)
|
||||||
{
|
{
|
||||||
@@ -152,6 +154,7 @@ public class KisApiClient : IKisApiClient
|
|||||||
foreach (var header in headers)
|
foreach (var header in headers)
|
||||||
request.Headers.Add(header.Key, header.Value);
|
request.Headers.Add(header.Key, header.Value);
|
||||||
|
|
||||||
|
await ApplyRateLimitDelayAsync(account);
|
||||||
var response = await _httpClient.SendAsync(request);
|
var response = await _httpClient.SendAsync(request);
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
@@ -195,6 +198,7 @@ public class KisApiClient : IKisApiClient
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
await ApplyRateLimitDelayAsync(creds.Account);
|
||||||
var response = await _httpClient.PostAsJsonAsync(
|
var response = await _httpClient.PostAsJsonAsync(
|
||||||
$"{creds.Domain}/oauth2/tokenP",
|
$"{creds.Domain}/oauth2/tokenP",
|
||||||
tokenRequest
|
tokenRequest
|
||||||
@@ -318,4 +322,29 @@ public class KisApiClient : IKisApiClient
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ApplyRateLimitDelayAsync(string account)
|
||||||
|
{
|
||||||
|
await _rateLimitSemaphore.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mode = account.Contains("real", StringComparison.OrdinalIgnoreCase) ? "real" : "mock";
|
||||||
|
int minIntervalMs = mode == "real" ? 150 : 400;
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var elapsed = (now - _lastRequestTime).TotalMilliseconds;
|
||||||
|
if (elapsed < minIntervalMs)
|
||||||
|
{
|
||||||
|
var delay = minIntervalMs - (int)elapsed;
|
||||||
|
_logger.LogDebug("Rate limit throttling: delaying for {Delay}ms (Mode: {Mode})", delay, mode);
|
||||||
|
await Task.Delay(delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastRequestTime = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_rateLimitSemaphore.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user