mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-06-12 10:26:27 +00:00
Merge branch 'master' of https://github.com/JKorf/CryptoExchange.Net
This commit is contained in:
commit
4ca2c1c516
@ -7,7 +7,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PackageId>CryptoExchange.Net</PackageId>
|
<PackageId>CryptoExchange.Net</PackageId>
|
||||||
<Authors>JKorf</Authors>
|
<Authors>JKorf</Authors>
|
||||||
<PackageVersion>0.0.36</PackageVersion>
|
<PackageVersion>0.0.37</PackageVersion>
|
||||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||||
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>
|
||||||
<PackageLicenseUrl>https://github.com/JKorf/CryptoExchange.Net/blob/master/LICENSE</PackageLicenseUrl>
|
<PackageLicenseUrl>https://github.com/JKorf/CryptoExchange.Net/blob/master/LICENSE</PackageLicenseUrl>
|
||||||
|
@ -3,12 +3,15 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
using System.Net.Sockets;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CryptoExchange.Net.Attributes;
|
using CryptoExchange.Net.Attributes;
|
||||||
using CryptoExchange.Net.Authentication;
|
using CryptoExchange.Net.Authentication;
|
||||||
using CryptoExchange.Net.Interfaces;
|
using CryptoExchange.Net.Interfaces;
|
||||||
using CryptoExchange.Net.Logging;
|
using CryptoExchange.Net.Logging;
|
||||||
|
using CryptoExchange.Net.Objects;
|
||||||
using CryptoExchange.Net.RateLimiter;
|
using CryptoExchange.Net.RateLimiter;
|
||||||
using CryptoExchange.Net.Requests;
|
using CryptoExchange.Net.Requests;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@ -20,8 +23,10 @@ namespace CryptoExchange.Net
|
|||||||
{
|
{
|
||||||
public IRequestFactory RequestFactory { get; set; } = new RequestFactory();
|
public IRequestFactory RequestFactory { get; set; } = new RequestFactory();
|
||||||
|
|
||||||
|
protected string baseAddress;
|
||||||
protected Log log;
|
protected Log log;
|
||||||
protected ApiProxy apiProxy;
|
protected ApiProxy apiProxy;
|
||||||
|
protected RateLimitingBehaviour rateLimitBehaviour;
|
||||||
|
|
||||||
protected AuthenticationProvider authProvider;
|
protected AuthenticationProvider authProvider;
|
||||||
private List<IRateLimiter> rateLimiters;
|
private List<IRateLimiter> rateLimiters;
|
||||||
@ -47,10 +52,12 @@ namespace CryptoExchange.Net
|
|||||||
log.UpdateWriters(exchangeOptions.LogWriters);
|
log.UpdateWriters(exchangeOptions.LogWriters);
|
||||||
log.Level = exchangeOptions.LogVerbosity;
|
log.Level = exchangeOptions.LogVerbosity;
|
||||||
|
|
||||||
|
baseAddress = exchangeOptions.BaseAddress;
|
||||||
apiProxy = exchangeOptions.Proxy;
|
apiProxy = exchangeOptions.Proxy;
|
||||||
if (apiProxy != null)
|
if (apiProxy != null)
|
||||||
log.Write(LogVerbosity.Info, $"Setting api proxy to {exchangeOptions.Proxy.Host}:{exchangeOptions.Proxy.Port}");
|
log.Write(LogVerbosity.Info, $"Setting api proxy to {exchangeOptions.Proxy.Host}:{exchangeOptions.Proxy.Port}");
|
||||||
|
|
||||||
|
rateLimitBehaviour = exchangeOptions.RateLimitingBehaviour;
|
||||||
rateLimiters = new List<IRateLimiter>();
|
rateLimiters = new List<IRateLimiter>();
|
||||||
foreach (var rateLimiter in exchangeOptions.RateLimiters)
|
foreach (var rateLimiter in exchangeOptions.RateLimiters)
|
||||||
rateLimiters.Add(rateLimiter);
|
rateLimiters.Add(rateLimiter);
|
||||||
@ -83,11 +90,49 @@ namespace CryptoExchange.Net
|
|||||||
authProvider = authentictationProvider;
|
authProvider = authentictationProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ping to see if the server is reachable
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The roundtrip time of the ping request</returns>
|
||||||
|
public CallResult<long> Ping() => PingAsync().Result;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ping to see if the server is reachable
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The roundtrip time of the ping request</returns>
|
||||||
|
public async Task<CallResult<long>> PingAsync()
|
||||||
|
{
|
||||||
|
var ping = new Ping();
|
||||||
|
var uri = new Uri(baseAddress);
|
||||||
|
PingReply reply = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
reply = await ping.SendPingAsync(uri.Host);
|
||||||
|
}
|
||||||
|
catch(PingException e)
|
||||||
|
{
|
||||||
|
if(e.InnerException != null)
|
||||||
|
{
|
||||||
|
if (e.InnerException is SocketException)
|
||||||
|
return new CallResult<long>(0, new CantConnectError() { Message = "Ping failed: " + ((SocketException)e.InnerException).SocketErrorCode });
|
||||||
|
else
|
||||||
|
return new CallResult<long>(0, new CantConnectError() { Message = "Ping failed: " + e.InnerException.Message });
|
||||||
|
}
|
||||||
|
return new CallResult<long>(0, new CantConnectError() { Message = "Ping failed: " + e.Message });
|
||||||
|
}
|
||||||
|
if (reply.Status == IPStatus.Success)
|
||||||
|
return new CallResult<long>(reply.RoundtripTime, null);
|
||||||
|
return new CallResult<long>(0, new CantConnectError() { Message = "Ping failed: " + reply.Status });
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual async Task<CallResult<T>> ExecuteRequest<T>(Uri uri, string method = "GET", Dictionary<string, object> parameters = null, bool signed = false) where T : class
|
protected virtual async Task<CallResult<T>> ExecuteRequest<T>(Uri uri, string method = "GET", Dictionary<string, object> parameters = null, bool signed = false) where T : class
|
||||||
{
|
{
|
||||||
log.Write(LogVerbosity.Debug, $"Creating request for " + uri);
|
log.Write(LogVerbosity.Debug, $"Creating request for " + uri);
|
||||||
if (signed && authProvider == null)
|
if (signed && authProvider == null)
|
||||||
|
{
|
||||||
|
log.Write(LogVerbosity.Warning, $"Request {uri.AbsolutePath} failed because no ApiCredentials were provided");
|
||||||
return new CallResult<T>(null, new NoApiCredentialsError());
|
return new CallResult<T>(null, new NoApiCredentialsError());
|
||||||
|
}
|
||||||
|
|
||||||
var request = ConstructRequest(uri, method, parameters, signed);
|
var request = ConstructRequest(uri, method, parameters, signed);
|
||||||
|
|
||||||
@ -99,9 +144,14 @@ namespace CryptoExchange.Net
|
|||||||
|
|
||||||
foreach (var limiter in rateLimiters)
|
foreach (var limiter in rateLimiters)
|
||||||
{
|
{
|
||||||
var limitedBy = limiter.LimitRequest(uri.AbsolutePath);
|
var limitResult = limiter.LimitRequest(uri.AbsolutePath, rateLimitBehaviour);
|
||||||
if (limitedBy > 0)
|
if (!limitResult.Success)
|
||||||
log.Write(LogVerbosity.Debug, $"Request {uri.AbsolutePath} was limited by {limitedBy}ms by {limiter.GetType().Name}");
|
{
|
||||||
|
log.Write(LogVerbosity.Debug, $"Request {uri.AbsolutePath} failed because of rate limit");
|
||||||
|
return new CallResult<T>(null, limitResult.Error);
|
||||||
|
}
|
||||||
|
else if (limitResult.Data > 0)
|
||||||
|
log.Write(LogVerbosity.Debug, $"Request {uri.AbsolutePath} was limited by {limitResult.Data}ms by {limiter.GetType().Name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
string paramString = null;
|
string paramString = null;
|
||||||
|
8
CryptoExchange.Net/Objects/Enums.cs
Normal file
8
CryptoExchange.Net/Objects/Enums.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace CryptoExchange.Net.Objects
|
||||||
|
{
|
||||||
|
public enum RateLimitingBehaviour
|
||||||
|
{
|
||||||
|
Fail,
|
||||||
|
Wait
|
||||||
|
}
|
||||||
|
}
|
@ -38,21 +38,26 @@
|
|||||||
|
|
||||||
public class WebError : Error
|
public class WebError : Error
|
||||||
{
|
{
|
||||||
public WebError(string message) : base(3, "Web error: " + message) { }
|
public WebError(string message) : base(4, "Web error: " + message) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DeserializeError : Error
|
public class DeserializeError : Error
|
||||||
{
|
{
|
||||||
public DeserializeError(string message) : base(4, "Error deserializing data: " + message) { }
|
public DeserializeError(string message) : base(5, "Error deserializing data: " + message) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UnknownError : Error
|
public class UnknownError : Error
|
||||||
{
|
{
|
||||||
public UnknownError(string message) : base(5, "Unknown error occured " + message) { }
|
public UnknownError(string message) : base(6, "Unknown error occured " + message) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ArgumentError : Error
|
public class ArgumentError : Error
|
||||||
{
|
{
|
||||||
public ArgumentError(string message) : base(5, "Invalid parameter: " + message) { }
|
public ArgumentError(string message) : base(7, "Invalid parameter: " + message) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RateLimitError: Error
|
||||||
|
{
|
||||||
|
public RateLimitError(string message) : base(8, "Rate limit exceeded: " + message) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,7 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using CryptoExchange.Net.Authentication;
|
using CryptoExchange.Net.Authentication;
|
||||||
using CryptoExchange.Net.Logging;
|
using CryptoExchange.Net.Logging;
|
||||||
|
using CryptoExchange.Net.Objects;
|
||||||
using CryptoExchange.Net.RateLimiter;
|
using CryptoExchange.Net.RateLimiter;
|
||||||
|
|
||||||
namespace CryptoExchange.Net
|
namespace CryptoExchange.Net
|
||||||
@ -17,6 +18,11 @@ namespace CryptoExchange.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ApiCredentials ApiCredentials { get; set; }
|
public ApiCredentials ApiCredentials { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The base address of the client
|
||||||
|
/// </summary>
|
||||||
|
public string BaseAddress { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Proxy to use
|
/// Proxy to use
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -36,5 +42,10 @@ namespace CryptoExchange.Net
|
|||||||
/// List of ratelimiters to use
|
/// List of ratelimiters to use
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<IRateLimiter> RateLimiters { get; set; } = new List<IRateLimiter>();
|
public List<IRateLimiter> RateLimiters { get; set; } = new List<IRateLimiter>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What to do when a call would exceed the rate limit
|
||||||
|
/// </summary>
|
||||||
|
public RateLimitingBehaviour RateLimitingBehaviour { get; set; } = RateLimitingBehaviour.Wait;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,9 @@
|
|||||||
namespace CryptoExchange.Net.RateLimiter
|
using CryptoExchange.Net.Objects;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.RateLimiter
|
||||||
{
|
{
|
||||||
public interface IRateLimiter
|
public interface IRateLimiter
|
||||||
{
|
{
|
||||||
double LimitRequest(string url);
|
CallResult<double> LimitRequest(string url, RateLimitingBehaviour limitBehaviour);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using CryptoExchange.Net.Objects;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -27,7 +28,7 @@ namespace CryptoExchange.Net.RateLimiter
|
|||||||
this.perTimePeriod = perTimePeriod;
|
this.perTimePeriod = perTimePeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double LimitRequest(string url)
|
public CallResult<double> LimitRequest(string url, RateLimitingBehaviour limitingBehaviour)
|
||||||
{
|
{
|
||||||
int waitTime;
|
int waitTime;
|
||||||
RateLimitObject rlo;
|
RateLimitObject rlo;
|
||||||
@ -49,6 +50,9 @@ namespace CryptoExchange.Net.RateLimiter
|
|||||||
waitTime = rlo.GetWaitTime(DateTime.UtcNow, limitPerEndpoint, perTimePeriod);
|
waitTime = rlo.GetWaitTime(DateTime.UtcNow, limitPerEndpoint, perTimePeriod);
|
||||||
if (waitTime != 0)
|
if (waitTime != 0)
|
||||||
{
|
{
|
||||||
|
if(limitingBehaviour == RateLimitingBehaviour.Fail)
|
||||||
|
return new CallResult<double>(waitTime, new RateLimitError($"endpoint limit of {limitPerEndpoint} reached on endpoint " + url));
|
||||||
|
|
||||||
Thread.Sleep(Convert.ToInt32(waitTime));
|
Thread.Sleep(Convert.ToInt32(waitTime));
|
||||||
waitTime += (int)sw.ElapsedMilliseconds;
|
waitTime += (int)sw.ElapsedMilliseconds;
|
||||||
}
|
}
|
||||||
@ -56,7 +60,7 @@ namespace CryptoExchange.Net.RateLimiter
|
|||||||
rlo.Add(DateTime.UtcNow);
|
rlo.Add(DateTime.UtcNow);
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitTime;
|
return new CallResult<double>(waitTime, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using CryptoExchange.Net.Objects;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -28,7 +29,7 @@ namespace CryptoExchange.Net.RateLimiter
|
|||||||
this.perTimePeriod = perTimePeriod;
|
this.perTimePeriod = perTimePeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double LimitRequest(string url)
|
public CallResult<double> LimitRequest(string url, RateLimitingBehaviour limitBehaviour)
|
||||||
{
|
{
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
lock (requestLock)
|
lock (requestLock)
|
||||||
@ -43,6 +44,9 @@ namespace CryptoExchange.Net.RateLimiter
|
|||||||
waitTime = (history.First() - (checkTime - perTimePeriod)).TotalMilliseconds;
|
waitTime = (history.First() - (checkTime - perTimePeriod)).TotalMilliseconds;
|
||||||
if (waitTime > 0)
|
if (waitTime > 0)
|
||||||
{
|
{
|
||||||
|
if (limitBehaviour == RateLimitingBehaviour.Fail)
|
||||||
|
return new CallResult<double>(waitTime, new RateLimitError($"total limit of {limit} reached"));
|
||||||
|
|
||||||
Thread.Sleep(Convert.ToInt32(waitTime));
|
Thread.Sleep(Convert.ToInt32(waitTime));
|
||||||
waitTime += sw.ElapsedMilliseconds;
|
waitTime += sw.ElapsedMilliseconds;
|
||||||
}
|
}
|
||||||
@ -50,7 +54,7 @@ namespace CryptoExchange.Net.RateLimiter
|
|||||||
|
|
||||||
history.Add(DateTime.UtcNow);
|
history.Add(DateTime.UtcNow);
|
||||||
history.Sort();
|
history.Sort();
|
||||||
return waitTime;
|
return new CallResult<double>(waitTime, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user