mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-06-10 01:16:24 +00:00
Added rate limiter for api key
This commit is contained in:
parent
f880ff9f18
commit
1a9263a8a3
@ -166,5 +166,37 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
Assert.IsTrue(result2.Success);
|
Assert.IsTrue(result2.Success);
|
||||||
Assert.IsTrue(sw.ElapsedMilliseconds > 900, $"Actual: {sw.ElapsedMilliseconds}");
|
Assert.IsTrue(sw.ElapsedMilliseconds > 900, $"Actual: {sw.ElapsedMilliseconds}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase]
|
||||||
|
public void SettingApiKeyRateLimiter_Should_DelayRequestsFromSameKey()
|
||||||
|
{
|
||||||
|
// arrange
|
||||||
|
var client = new TestRestClient(new ClientOptions()
|
||||||
|
{
|
||||||
|
RateLimiters = new List<IRateLimiter> { new RateLimiterAPIKey(1, TimeSpan.FromSeconds(1)) },
|
||||||
|
RateLimitingBehaviour = RateLimitingBehaviour.Wait,
|
||||||
|
LogVerbosity = LogVerbosity.Debug,
|
||||||
|
ApiCredentials = new ApiCredentials("TestKey", "TestSecret")
|
||||||
|
});
|
||||||
|
client.SetResponse("{\"property\": 123}");
|
||||||
|
|
||||||
|
|
||||||
|
// act
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
var result1 = client.Request<TestObject>().Result;
|
||||||
|
client.SetKey("TestKey2", "TestSecret2"); // set to different key
|
||||||
|
client.SetResponse("{\"property\": 123}"); // reset response stream
|
||||||
|
var result2 = client.Request<TestObject>().Result;
|
||||||
|
client.SetKey("TestKey", "TestSecret"); // set back to original key, should delay
|
||||||
|
client.SetResponse("{\"property\": 123}"); // reset response stream
|
||||||
|
var result3 = client.Request<TestObject>().Result;
|
||||||
|
sw.Stop();
|
||||||
|
|
||||||
|
// assert
|
||||||
|
Assert.IsTrue(result1.Success);
|
||||||
|
Assert.IsTrue(result2.Success);
|
||||||
|
Assert.IsTrue(result3.Success);
|
||||||
|
Assert.IsTrue(sw.ElapsedMilliseconds > 900 && sw.ElapsedMilliseconds < 1900, $"Actual: {sw.ElapsedMilliseconds}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ using System.Net.Http;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CryptoExchange.Net.Authentication;
|
||||||
|
|
||||||
namespace CryptoExchange.Net.UnitTests.TestImplementations
|
namespace CryptoExchange.Net.UnitTests.TestImplementations
|
||||||
{
|
{
|
||||||
@ -25,6 +26,11 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|||||||
RequestFactory = new Mock<IRequestFactory>().Object;
|
RequestFactory = new Mock<IRequestFactory>().Object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetKey(string key, string secret)
|
||||||
|
{
|
||||||
|
SetAuthenticationProvider(new UnitTests.TestAuthProvider(new ApiCredentials(key, secret)));
|
||||||
|
}
|
||||||
|
|
||||||
public void SetResponse(string responseData, Stream requestStream = null)
|
public void SetResponse(string responseData, Stream requestStream = null)
|
||||||
{
|
{
|
||||||
var expectedBytes = Encoding.UTF8.GetBytes(responseData);
|
var expectedBytes = Encoding.UTF8.GetBytes(responseData);
|
||||||
@ -92,6 +98,13 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class TestAuthProvider : AuthenticationProvider
|
||||||
|
{
|
||||||
|
public TestAuthProvider(ApiCredentials credentials) : base(credentials)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class ParseErrorTestRestClient: TestRestClient
|
public class ParseErrorTestRestClient: TestRestClient
|
||||||
{
|
{
|
||||||
public ParseErrorTestRestClient() { }
|
public ParseErrorTestRestClient() { }
|
||||||
|
@ -17,7 +17,7 @@ namespace CryptoExchange.Net
|
|||||||
public string BaseAddress { get; private set; }
|
public string BaseAddress { get; private set; }
|
||||||
protected internal Log log;
|
protected internal Log log;
|
||||||
protected ApiProxy apiProxy;
|
protected ApiProxy apiProxy;
|
||||||
protected AuthenticationProvider authProvider;
|
protected internal AuthenticationProvider authProvider;
|
||||||
|
|
||||||
protected static int lastId;
|
protected static int lastId;
|
||||||
protected static object idLock = new object();
|
protected static object idLock = new object();
|
||||||
|
73
CryptoExchange.Net/RateLimiter/RateLimiterAPIKey.cs
Normal file
73
CryptoExchange.Net/RateLimiter/RateLimiterAPIKey.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading;
|
||||||
|
using CryptoExchange.Net.Interfaces;
|
||||||
|
using CryptoExchange.Net.Objects;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.RateLimiter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Limits the amount of requests per time period to a certain limit, counts the request per API key.
|
||||||
|
/// </summary>
|
||||||
|
public class RateLimiterAPIKey: IRateLimiter
|
||||||
|
{
|
||||||
|
internal Dictionary<string, RateLimitObject> history = new Dictionary<string, RateLimitObject>();
|
||||||
|
|
||||||
|
private readonly int limitPerKey;
|
||||||
|
private readonly TimeSpan perTimePeriod;
|
||||||
|
private readonly object historyLock = new object();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new RateLimiterAPIKey. This rate limiter limits the amount of requests per time period to a certain limit, counts the request per API key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="limitPerApiKey">The amount to limit to</param>
|
||||||
|
/// <param name="perTimePeriod">The time period over which the limit counts</param>
|
||||||
|
public RateLimiterAPIKey(int limitPerApiKey, TimeSpan perTimePeriod)
|
||||||
|
{
|
||||||
|
limitPerKey = limitPerApiKey;
|
||||||
|
this.perTimePeriod = perTimePeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public CallResult<double> LimitRequest(RestClient client, string url, RateLimitingBehaviour limitBehaviour)
|
||||||
|
{
|
||||||
|
if(client.authProvider?.Credentials == null)
|
||||||
|
return new CallResult<double>(0, null);
|
||||||
|
|
||||||
|
string key = client.authProvider.Credentials.Key.GetString();
|
||||||
|
|
||||||
|
int waitTime;
|
||||||
|
RateLimitObject rlo;
|
||||||
|
lock (historyLock)
|
||||||
|
{
|
||||||
|
if (history.ContainsKey(key))
|
||||||
|
rlo = history[key];
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rlo = new RateLimitObject();
|
||||||
|
history.Add(key, rlo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
lock (rlo.LockObject)
|
||||||
|
{
|
||||||
|
sw.Stop();
|
||||||
|
waitTime = rlo.GetWaitTime(DateTime.UtcNow, limitPerKey, perTimePeriod);
|
||||||
|
if (waitTime != 0)
|
||||||
|
{
|
||||||
|
if (limitBehaviour == RateLimitingBehaviour.Fail)
|
||||||
|
return new CallResult<double>(waitTime, new RateLimitError($"endpoint limit of {limitPerKey} reached on api key " + key));
|
||||||
|
|
||||||
|
Thread.Sleep(Convert.ToInt32(waitTime));
|
||||||
|
waitTime += (int)sw.ElapsedMilliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
rlo.Add(DateTime.UtcNow);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CallResult<double>(waitTime, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user