1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-06-12 10:26:27 +00:00
This commit is contained in:
JKorf 2018-08-13 12:19:57 +02:00
commit 4ca2c1c516
10 changed files with 100 additions and 16 deletions

View File

@ -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>

View File

@ -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;

View File

@ -0,0 +1,8 @@
namespace CryptoExchange.Net.Objects
{
public enum RateLimitingBehaviour
{
Fail,
Wait
}
}

View File

@ -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) { }
} }
} }

View File

@ -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;
} }
} }

View File

@ -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);
} }
} }

View File

@ -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);
} }
} }
} }

View File

@ -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);
} }
} }
} }