1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-08-31 12:42:00 +00:00
This commit is contained in:
JKorf 2025-08-25 19:11:50 +02:00
parent 9ba29035b2
commit 73764970b0
17 changed files with 957 additions and 995 deletions

View File

@ -1,132 +0,0 @@
//using System;
//using System.IO;
//using System.Net.WebSockets;
//using System.Security.Authentication;
//using System.Text;
//using System.Threading.Tasks;
//using CryptoExchange.Net.Interfaces;
//using CryptoExchange.Net.Objects;
//namespace CryptoExchange.Net.UnitTests.TestImplementations
//{
// public class TestSocket: IWebsocket
// {
// public bool CanConnect { get; set; }
// public bool Connected { get; set; }
// public event Func<Task> OnClose;
//#pragma warning disable 0067
// public event Func<Task> OnReconnected;
// public event Func<Task> OnReconnecting;
// public event Func<int, Task> OnRequestRateLimited;
//#pragma warning restore 0067
// public event Func<int, Task> OnRequestSent;
// public event Func<WebSocketMessageType, ReadOnlyMemory<byte>, Task> OnStreamMessage;
// public event Func<Exception, Task> OnError;
// public event Func<Task> OnOpen;
// public Func<Task<Uri>> GetReconnectionUrl { get; set; }
// public int Id { get; }
// public bool ShouldReconnect { get; set; }
// public TimeSpan Timeout { get; set; }
// public Func<string, string> DataInterpreterString { get; set; }
// public Func<byte[], string> DataInterpreterBytes { get; set; }
// public DateTime? DisconnectTime { get; set; }
// public string Url { get; }
// public bool IsClosed => !Connected;
// public bool IsOpen => Connected;
// public bool PingConnection { get; set; }
// public TimeSpan PingInterval { get; set; }
// public SslProtocols SSLProtocols { get; set; }
// public Encoding Encoding { get; set; }
// public int ConnectCalls { get; private set; }
// public bool Reconnecting { get; set; }
// public string Origin { get; set; }
// public int? RatelimitPerSecond { get; set; }
// public double IncomingKbps => throw new NotImplementedException();
// public Uri Uri => new Uri("");
// public TimeSpan KeepAliveInterval { get; set; }
// public static int lastId = 0;
// public static object lastIdLock = new object();
// public TestSocket()
// {
// lock (lastIdLock)
// {
// Id = lastId + 1;
// lastId++;
// }
// }
// public Task<CallResult> ConnectAsync()
// {
// Connected = CanConnect;
// ConnectCalls++;
// if (CanConnect)
// InvokeOpen();
// return Task.FromResult(CanConnect ? new CallResult(null) : new CallResult(new CantConnectError()));
// }
// public bool Send(int requestId, string data, int weight)
// {
// if(!Connected)
// throw new Exception("Socket not connected");
// OnRequestSent?.Invoke(requestId);
// return true;
// }
// public void Reset()
// {
// }
// public Task CloseAsync()
// {
// Connected = false;
// DisconnectTime = DateTime.UtcNow;
// OnClose?.Invoke();
// return Task.FromResult(0);
// }
// public void SetProxy(string host, int port)
// {
// throw new NotImplementedException();
// }
// public void Dispose()
// {
// }
// public void InvokeClose()
// {
// Connected = false;
// DisconnectTime = DateTime.UtcNow;
// Reconnecting = true;
// OnClose?.Invoke();
// }
// public void InvokeOpen()
// {
// OnOpen?.Invoke();
// }
// public void InvokeMessage(string data)
// {
// OnStreamMessage?.Invoke(WebSocketMessageType.Text, new ReadOnlyMemory<byte>(Encoding.UTF8.GetBytes(data))).Wait();
// }
// public void SetProxy(ApiProxy proxy)
// {
// throw new NotImplementedException();
// }
// public void InvokeError(Exception error)
// {
// OnError?.Invoke(error);
// }
// public Task ReconnectAsync() => Task.CompletedTask;
// }
//}

View File

@ -7,6 +7,7 @@ indent_style = space
indent_size = 4
trim_trailing_whitespace = true
charset = utf-8
max_line_length = 140
insert_final_newline = true
# ReSharper code style properties

View File

@ -465,7 +465,11 @@ public abstract class AuthenticationProvider
public abstract class AuthenticationProvider<TApiCredentials> : AuthenticationProvider where TApiCredentials : ApiCredentials
{
/// <inheritdoc />
#pragma warning disable IDE1006 // Naming Styles
#pragma warning disable CA1707 // Naming Styles
protected new TApiCredentials _credentials => (TApiCredentials)base._credentials;
#pragma warning restore IDE1006 // Naming Styles
#pragma warning restore CA1707 // Naming Styles
/// <summary>
/// ctor

View File

@ -124,7 +124,16 @@ public abstract class BaseApiClient : IDisposable, IBaseApiClient
/// <summary>
/// Dispose
/// </summary>
public virtual void Dispose()
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose
/// </summary>
public virtual void Dispose(bool disposing)
{
_disposing = true;
}

View File

@ -119,10 +119,24 @@ public abstract class BaseClient : IDisposable
/// <summary>
/// Dispose
/// </summary>
public virtual void Dispose()
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose
/// </summary>
public virtual void Dispose(bool disposing)
{
if (disposing)
{
_logger.Log(LogLevel.Debug, "Disposing client");
foreach (var client in ApiClients)
client.Dispose();
}
}
}

View File

@ -59,8 +59,20 @@ public class CryptoBaseClient : IDisposable
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
public void Dispose(bool disposing)
{
if (disposing)
{
_serviceCache.Clear();
}
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}

View File

@ -18,13 +18,13 @@ using CryptoExchange.Net.RateLimiting.Interfaces;
using CryptoExchange.Net.Requests;
using Microsoft.Extensions.Logging;
namespace CryptoExchange.Net.Clients
namespace CryptoExchange.Net.Clients;
/// <summary>
/// Base rest API client for interacting with a REST API
/// </summary>
public abstract class RestApiClient : BaseApiClient, IRestApiClient
{
/// <summary>
/// Base rest API client for interacting with a REST API
/// </summary>
public abstract class RestApiClient : BaseApiClient, IRestApiClient
{
/// <inheritdoc />
public IRequestFactory RequestFactory { get; set; } = new RequestFactory();
@ -637,7 +637,7 @@ namespace CryptoExchange.Net.Clients
{
// Handle retry after header
var retryAfterHeader = responseHeaders.SingleOrDefault(r => r.Key.Equals("Retry-After", StringComparison.InvariantCultureIgnoreCase));
if (retryAfterHeader.Value?.Any() != true)
if (!(retryAfterHeader.Value.Length > 0))
return new ServerRateLimitError();
var value = retryAfterHeader.Value.First();
@ -724,5 +724,4 @@ namespace CryptoExchange.Net.Clients
=> ClientOptions.CachingEnabled
&& definition.Method == HttpMethod.Get
&& !definition.PreventCaching;
}
}

View File

@ -839,8 +839,11 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
/// <summary>
/// Dispose the client
/// </summary>
public override void Dispose()
public override void Dispose(bool disposing)
{
if (disposing)
return;
_disposing = true;
var tasks = new List<Task>();
{
@ -855,7 +858,7 @@ public abstract class SocketApiClient : BaseApiClient, ISocketApiClient
}
semaphoreSlim?.Dispose();
base.Dispose();
base.Dispose(disposing);
}
/// <summary>

View File

@ -117,7 +117,19 @@ public class AsyncResetEvent : IDisposable
/// Dispose
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose(bool disposing)
{
if (disposing)
{
_waits.Clear();
}
}
}

View File

@ -1,13 +1,13 @@
using CryptoExchange.Net.RateLimiting.Interfaces;
using CryptoExchange.Net.RateLimiting.Interfaces;
using System.Net.Http;
namespace CryptoExchange.Net.Objects
namespace CryptoExchange.Net.Objects;
/// <summary>
/// The definition of a rest request
/// </summary>
public class RequestDefinition
{
/// <summary>
/// The definition of a rest request
/// </summary>
public class RequestDefinition
{
private string? _stringRep;
// Basics
@ -91,5 +91,4 @@ namespace CryptoExchange.Net.Objects
{
return _stringRep ??= $"{Method} {Path}{(Authenticated ? " authenticated" : "")}";
}
}
}

View File

@ -1,14 +1,14 @@
using CryptoExchange.Net.RateLimiting.Interfaces;
using CryptoExchange.Net.RateLimiting.Interfaces;
using System.Collections.Concurrent;
using System.Net.Http;
namespace CryptoExchange.Net.Objects
namespace CryptoExchange.Net.Objects;
/// <summary>
/// Request definitions cache
/// </summary>
public class RequestDefinitionCache
{
/// <summary>
/// Request definitions cache
/// </summary>
public class RequestDefinitionCache
{
private readonly ConcurrentDictionary<string, RequestDefinition> _definitions = new();
/// <summary>
@ -112,5 +112,4 @@ namespace CryptoExchange.Net.Objects
return def;
}
}
}

View File

@ -22,8 +22,22 @@ public class TraceLoggerProvider : ILoggerProvider
/// <inheritdoc />
public ILogger CreateLogger(string categoryName) => new TraceLogger(categoryName, _logLevel);
/// <inheritdoc />
public void Dispose() { }
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose
/// </summary>
public static void Dispose(bool disposing)
{
}
}
/// <summary>

View File

@ -577,6 +577,8 @@ public abstract class SymbolOrderBook : ISymbolOrderBook, IDisposable
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Status = OrderBookStatus.Disposing;
@ -594,6 +596,7 @@ public abstract class SymbolOrderBook : ISymbolOrderBook, IDisposable
Status = OrderBookStatus.Disposed;
}
}
/// <summary>
/// String representation of the top 3 entries

View File

@ -488,9 +488,18 @@ public class CryptoExchangeWebSocketClient : IWebsocket
}
/// <summary>
/// Dispose the socket
/// Dispose
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose the socket
/// </summary>
public void Dispose(bool disposing)
{
if (_disposed)
return;

View File

@ -169,7 +169,6 @@ public abstract class Query : IMessageProcessor, IDisposable
{
if (disposing)
{
// TODO: dispose managed state (managed objects)
_cts?.Dispose();
_event.Dispose();
}

View File

@ -709,15 +709,30 @@ public class SocketConnection : IDisposable
}
/// <summary>
/// Dispose the connection
/// Dispose
/// </summary>
public void Dispose()
protected virtual void Dispose(bool disposing)
{
if (Status != SocketStatus.Disposed)
{
if (disposing)
{
Status = SocketStatus.Disposed;
periodicEvent?.Set();
periodicEvent?.Dispose();
_socket.Dispose();
}
}
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Whether or not a new subscription can be added to this connection

View File

@ -23,6 +23,8 @@ public class RestRequestValidator<TClient> where TClient : BaseRestClient
private readonly string _baseAddress;
private readonly string? _nestedPropertyForCompare;
private static readonly char[] _paramSeparator = new char[] { '?' };
/// <summary>
/// ctor
/// </summary>
@ -117,8 +119,8 @@ public class RestRequestValidator<TClient> where TClient : BaseRestClient
throw new Exception(name + $" authentication not matched. Expected: {expectedAuth}, Actual: {_isAuthenticated(result.AsDataless())}");
if (result.RequestMethod != new HttpMethod(expectedMethod!))
throw new Exception(name + $" http method not matched. Expected {expectedMethod}, Actual: {result.RequestMethod}");
if (expectedPath != result.RequestUrl!.Replace(_baseAddress, "").Split(new char[] { '?' })[0])
throw new Exception(name + $" path not matched. Expected: {expectedPath}, Actual: {result.RequestUrl!.Replace(_baseAddress, "").Split(new char[] { '?' })[0]}");
if (expectedPath != result.RequestUrl!.Replace(_baseAddress, "").Split(_paramSeparator)[0])
throw new Exception(name + $" path not matched. Expected: {expectedPath}, Actual: {result.RequestUrl!.Replace(_baseAddress, "").Split(_paramSeparator)[0]}");
if (!skipResponseValidation)
{
@ -176,8 +178,8 @@ public class RestRequestValidator<TClient> where TClient : BaseRestClient
throw new Exception(name + $" authentication not matched. Expected: {expectedAuth}, Actual: {_isAuthenticated(result)}");
if (result.RequestMethod != new HttpMethod(expectedMethod!))
throw new Exception(name + $" http method not matched. Expected {expectedMethod}, Actual: {result.RequestMethod}");
if (expectedPath != result.RequestUrl!.Replace(_baseAddress, "").Split(new char[] { '?' })[0])
throw new Exception(name + $" path not matched. Expected: {expectedPath}, Actual: {result.RequestUrl!.Replace(_baseAddress, "").Split(new char[] { '?' })[0]}");
if (expectedPath != result.RequestUrl!.Replace(_baseAddress, "").Split(_paramSeparator)[0])
throw new Exception(name + $" path not matched. Expected: {expectedPath}, Actual: {result.RequestUrl!.Replace(_baseAddress, "").Split(_paramSeparator)[0]}");
Trace.Listeners.Remove(listener);
}