1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-06-11 01:46:12 +00:00
This commit is contained in:
Jkorf 2021-11-03 08:27:03 +01:00
parent 23bbf0ef88
commit f83127590a
11 changed files with 335 additions and 672 deletions

View File

@ -26,7 +26,7 @@ namespace CryptoExchange.Net.UnitTests
//assert
Assert.IsTrue(client.BaseAddress == "http://test.address.com/");
Assert.IsTrue(client.ReconnectInterval.TotalSeconds == 6);
Assert.IsTrue(client.ClientOptions.ReconnectInterval.TotalSeconds == 6);
}
[TestCase(true)]

View File

@ -21,35 +21,19 @@ namespace CryptoExchange.Net
/// </summary>
public abstract class BaseClient : IDisposable
{
/// <summary>
/// The address of the client
/// </summary>
public string BaseAddress { get; }
/// <summary>
/// The name of the exchange the client is for
/// </summary>
public string ExchangeName { get; }
internal string ExchangeName { get; }
/// <summary>
/// The log object
/// </summary>
protected internal Log log;
/// <summary>
/// The api proxy
/// </summary>
protected ApiProxy? apiProxy;
/// <summary>
/// The authentication provider
/// </summary>
protected internal AuthenticationProvider? authProvider;
/// <summary>
/// Should check objects for missing properties based on the model and the received JSON
/// </summary>
public bool ShouldCheckObjects { get; set; }
/// <summary>
/// If true, the CallResult and DataEvent objects should also contain the originally received json data in the OriginalDaa property
/// </summary>
public bool OutputOriginalData { get; private set; }
/// <summary>
/// The last used id, use NextId() to get the next id and up this
/// </summary>
protected static int lastId;
@ -68,9 +52,9 @@ namespace CryptoExchange.Net
});
/// <summary>
/// Last id used
/// Provided client options
/// </summary>
public static int LastId => lastId;
public ClientOptions ClientOptions { get; }
/// <summary>
/// ctor
@ -85,13 +69,12 @@ namespace CryptoExchange.Net
log.UpdateWriters(options.LogWriters);
log.Level = options.LogLevel;
ClientOptions = options;
ExchangeName = exchangeName;
OutputOriginalData = options.OutputOriginalData;
BaseAddress = options.BaseAddress;
apiProxy = options.Proxy;
//BaseAddress = options.BaseAddress;
log.Write(LogLevel.Debug, $"Client configuration: {options}, CryptoExchange.Net: v{typeof(BaseClient).Assembly.GetName().Version}, {ExchangeName}.Net: v{GetType().Assembly.GetName().Version}");
ShouldCheckObjects = options.ShouldCheckObjects;
}
/// <summary>
@ -145,11 +128,10 @@ namespace CryptoExchange.Net
/// </summary>
/// <typeparam name="T">The type to deserialize into</typeparam>
/// <param name="data">The data to deserialize</param>
/// <param name="checkObject">Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug)</param>
/// <param name="serializer">A specific serializer to use</param>
/// <param name="requestId">Id of the request the data is returned from (used for grouping logging by request)</param>
/// <returns></returns>
protected CallResult<T> Deserialize<T>(string data, bool? checkObject = null, JsonSerializer? serializer = null, int? requestId = null)
protected CallResult<T> Deserialize<T>(string data, JsonSerializer? serializer = null, int? requestId = null)
{
var tokenResult = ValidateJson(data);
if (!tokenResult)
@ -158,7 +140,7 @@ namespace CryptoExchange.Net
return new CallResult<T>(default, tokenResult.Error);
}
return Deserialize<T>(tokenResult.Data, checkObject, serializer, requestId);
return Deserialize<T>(tokenResult.Data, serializer, requestId);
}
/// <summary>
@ -166,39 +148,16 @@ namespace CryptoExchange.Net
/// </summary>
/// <typeparam name="T">The type to deserialize into</typeparam>
/// <param name="obj">The data to deserialize</param>
/// <param name="checkObject">Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug)</param>
/// <param name="serializer">A specific serializer to use</param>
/// <param name="requestId">Id of the request the data is returned from (used for grouping logging by request)</param>
/// <returns></returns>
protected CallResult<T> Deserialize<T>(JToken obj, bool? checkObject = null, JsonSerializer? serializer = null, int? requestId = null)
protected CallResult<T> Deserialize<T>(JToken obj, JsonSerializer? serializer = null, int? requestId = null)
{
if (serializer == null)
serializer = defaultSerializer;
try
{
if ((checkObject ?? ShouldCheckObjects)&& log.Level <= LogLevel.Debug)
{
// This checks the input JToken object against the class it is being serialized into and outputs any missing fields
// in either the input or the class
try
{
if (obj is JObject o)
{
CheckObject(typeof(T), o, requestId);
}
else if (obj is JArray j)
{
if (j.HasValues && j[0] is JObject jObject)
CheckObject(typeof(T).GetElementType(), jObject, requestId);
}
}
catch (Exception e)
{
log.Write(LogLevel.Debug, $"{(requestId != null ? $"[{ requestId}] " : "")}Failed to check response data: " + (e.InnerException?.Message ?? e.Message));
}
}
return new CallResult<T>(obj.ToObject<T>(serializer), null);
}
catch (JsonReaderException jre)
@ -243,12 +202,12 @@ namespace CryptoExchange.Net
// If we have to output the original json data or output the data into the logging we'll have to read to full response
// in order to log/return the json data
if (OutputOriginalData || log.Level <= LogLevel.Debug)
if (ClientOptions.OutputOriginalData || log.Level <= LogLevel.Debug)
{
var data = await reader.ReadToEndAsync().ConfigureAwait(false);
log.Write(LogLevel.Debug, $"{(requestId != null ? $"[{requestId}] ": "")}Response received{(elapsedMilliseconds != null ? $" in {elapsedMilliseconds}" : " ")}ms: {data}");
var result = Deserialize<T>(data, null, serializer, requestId);
if(OutputOriginalData)
var result = Deserialize<T>(data, serializer, requestId);
if(ClientOptions.OutputOriginalData)
result.OriginalData = data;
return result;
}
@ -308,116 +267,6 @@ namespace CryptoExchange.Net
return await reader.ReadToEndAsync().ConfigureAwait(false);
}
private void CheckObject(Type type, JObject obj, int? requestId = null)
{
if (type == null)
return;
if (type.GetCustomAttribute<JsonConverterAttribute>(true) != null)
// If type has a custom JsonConverter we assume this will handle property mapping
return;
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
return;
if (!obj.HasValues && type != typeof(object))
{
log.Write(LogLevel.Warning, $"{(requestId != null ? $"[{requestId}] " : "")}Expected `{type.Name}`, but received object was empty");
return;
}
var isDif = false;
var properties = new List<string>();
var props = type.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy);
foreach (var prop in props)
{
var attr = prop.GetCustomAttributes(typeof(JsonPropertyAttribute), false).FirstOrDefault();
var ignore = prop.GetCustomAttributes(typeof(JsonIgnoreAttribute), false).FirstOrDefault();
if (ignore != null)
continue;
var propertyName = ((JsonPropertyAttribute?) attr)?.PropertyName;
properties.Add(propertyName ?? prop.Name);
}
foreach (var token in obj)
{
var d = properties.FirstOrDefault(p => p == token.Key);
if (d == null)
{
d = properties.SingleOrDefault(p => string.Equals(p, token.Key, StringComparison.CurrentCultureIgnoreCase));
if (d == null)
{
if (!(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)))
{
log.Write(LogLevel.Warning, $"{(requestId != null ? $"[{requestId}] " : "")}Local object doesn't have property `{token.Key}` expected in type `{type.Name}`");
isDif = true;
}
continue;
}
}
properties.Remove(d);
var propType = GetProperty(d, props)?.PropertyType;
if (propType == null || token.Value == null)
continue;
if (!IsSimple(propType) && propType != typeof(DateTime))
{
if (propType.IsArray && token.Value.HasValues && ((JArray)token.Value).Any() && ((JArray)token.Value)[0] is JObject)
CheckObject(propType.GetElementType()!, (JObject)token.Value[0]!, requestId);
else if (token.Value is JObject o)
CheckObject(propType, o, requestId);
}
}
foreach (var prop in properties)
{
var propInfo = props.First(p => p.Name == prop ||
((JsonPropertyAttribute)p.GetCustomAttributes(typeof(JsonPropertyAttribute), false).FirstOrDefault())?.PropertyName == prop);
var optional = propInfo.GetCustomAttributes(typeof(JsonOptionalPropertyAttribute), false).FirstOrDefault();
if (optional != null)
continue;
isDif = true;
log.Write(LogLevel.Warning, $"{(requestId != null ? $"[{requestId}] " : "")}Local object has property `{prop}` but was not found in received object of type `{type.Name}`");
}
if (isDif)
log.Write(LogLevel.Debug, $"{(requestId != null ? $"[{ requestId}] " : "")}Returned data: " + obj);
}
private static PropertyInfo? GetProperty(string name, IEnumerable<PropertyInfo> props)
{
foreach (var prop in props)
{
var attr = prop.GetCustomAttributes(typeof(JsonPropertyAttribute), false).FirstOrDefault();
if (attr == null)
{
if (string.Equals(prop.Name, name, StringComparison.CurrentCultureIgnoreCase))
return prop;
}
else
{
if (((JsonPropertyAttribute)attr).PropertyName == name)
return prop;
}
}
return null;
}
private static bool IsSimple(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
// nullable type, check if the nested type is simple.
return IsSimple(type.GetGenericArguments()[0]);
}
return type.IsPrimitive
|| type.IsEnum
|| type == typeof(string)
|| type == typeof(decimal);
}
/// <summary>
/// Generate a new unique id. The id is staticly stored so it is guarenteed to be unique across different client instances
/// </summary>

View File

@ -204,11 +204,6 @@
The base for all clients, websocket client and rest client
</summary>
</member>
<member name="P:CryptoExchange.Net.BaseClient.BaseAddress">
<summary>
The address of the client
</summary>
</member>
<member name="P:CryptoExchange.Net.BaseClient.ExchangeName">
<summary>
The name of the exchange the client is for
@ -219,26 +214,11 @@
The log object
</summary>
</member>
<member name="F:CryptoExchange.Net.BaseClient.apiProxy">
<summary>
The api proxy
</summary>
</member>
<member name="F:CryptoExchange.Net.BaseClient.authProvider">
<summary>
The authentication provider
</summary>
</member>
<member name="P:CryptoExchange.Net.BaseClient.ShouldCheckObjects">
<summary>
Should check objects for missing properties based on the model and the received JSON
</summary>
</member>
<member name="P:CryptoExchange.Net.BaseClient.OutputOriginalData">
<summary>
If true, the CallResult and DataEvent objects should also contain the originally received json data in the OriginalDaa property
</summary>
</member>
<member name="F:CryptoExchange.Net.BaseClient.lastId">
<summary>
The last used id, use NextId() to get the next id and up this
@ -254,9 +234,9 @@
A default serializer
</summary>
</member>
<member name="P:CryptoExchange.Net.BaseClient.LastId">
<member name="P:CryptoExchange.Net.BaseClient.ClientOptions">
<summary>
Last id used
Provided client options
</summary>
</member>
<member name="M:CryptoExchange.Net.BaseClient.#ctor(System.String,CryptoExchange.Net.Objects.ClientOptions,CryptoExchange.Net.Authentication.AuthenticationProvider)">
@ -280,24 +260,22 @@
<param name="data">The data to parse</param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.BaseClient.Deserialize``1(System.String,System.Nullable{System.Boolean},Newtonsoft.Json.JsonSerializer,System.Nullable{System.Int32})">
<member name="M:CryptoExchange.Net.BaseClient.Deserialize``1(System.String,Newtonsoft.Json.JsonSerializer,System.Nullable{System.Int32})">
<summary>
Deserialize a string into an object
</summary>
<typeparam name="T">The type to deserialize into</typeparam>
<param name="data">The data to deserialize</param>
<param name="checkObject">Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug)</param>
<param name="serializer">A specific serializer to use</param>
<param name="requestId">Id of the request the data is returned from (used for grouping logging by request)</param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.BaseClient.Deserialize``1(Newtonsoft.Json.Linq.JToken,System.Nullable{System.Boolean},Newtonsoft.Json.JsonSerializer,System.Nullable{System.Int32})">
<member name="M:CryptoExchange.Net.BaseClient.Deserialize``1(Newtonsoft.Json.Linq.JToken,Newtonsoft.Json.JsonSerializer,System.Nullable{System.Int32})">
<summary>
Deserialize a JToken into an object
</summary>
<typeparam name="T">The type to deserialize into</typeparam>
<param name="obj">The data to deserialize</param>
<param name="checkObject">Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug)</param>
<param name="serializer">A specific serializer to use</param>
<param name="requestId">Id of the request the data is returned from (used for grouping logging by request)</param>
<returns></returns>
@ -1233,31 +1211,11 @@
The factory for creating requests. Used for unit testing
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.IRestClient.RateLimitBehaviour">
<summary>
What should happen when hitting a rate limit
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.IRestClient.RateLimiters">
<summary>
List of active rate limiters
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.IRestClient.TotalRequestsMade">
<summary>
The total amount of requests made
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.IRestClient.BaseAddress">
<summary>
The base address of the API
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.IRestClient.ExchangeName">
<summary>
Client name
</summary>
</member>
<member name="M:CryptoExchange.Net.Interfaces.IRestClient.AddRateLimiter(CryptoExchange.Net.Interfaces.IRateLimiter)">
<summary>
Adds a rate limiter to the client. There are 2 choices, the <see cref="T:CryptoExchange.Net.RateLimiter.RateLimiterTotal"/> and the <see cref="T:CryptoExchange.Net.RateLimiter.RateLimiterPerEndpoint"/>.
@ -1269,69 +1227,24 @@
Removes all rate limiters from this client
</summary>
</member>
<member name="M:CryptoExchange.Net.Interfaces.IRestClient.Ping(System.Threading.CancellationToken)">
<member name="P:CryptoExchange.Net.Interfaces.IRestClient.ClientOptions">
<summary>
Ping to see if the server is reachable
Client options
</summary>
<returns>The roundtrip time of the ping request</returns>
</member>
<member name="M:CryptoExchange.Net.Interfaces.IRestClient.PingAsync(System.Threading.CancellationToken)">
<summary>
Ping to see if the server is reachable
</summary>
<returns>The roundtrip time of the ping request</returns>
</member>
<member name="T:CryptoExchange.Net.Interfaces.ISocketClient">
<summary>
Base class for socket API implementations
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.ISocketClient.SocketFactory">
<member name="P:CryptoExchange.Net.Interfaces.ISocketClient.ClientOptions">
<summary>
The factory for creating sockets. Used for unit testing
Client options
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.ISocketClient.ReconnectInterval">
<summary>
The time in between reconnect attempts
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.ISocketClient.AutoReconnect">
<summary>
Whether the client should try to auto reconnect when losing connection
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.ISocketClient.BaseAddress">
<summary>
The base address of the API
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.ISocketClient.ResponseTimeout">
<inheritdoc cref="P:CryptoExchange.Net.Objects.SocketClientOptions.SocketResponseTimeout"/>
</member>
<member name="P:CryptoExchange.Net.Interfaces.ISocketClient.SocketNoDataTimeout">
<inheritdoc cref="P:CryptoExchange.Net.Objects.SocketClientOptions.SocketNoDataTimeout"/>
</member>
<member name="P:CryptoExchange.Net.Interfaces.ISocketClient.MaxSocketConnections">
<summary>
The max amount of concurrent socket connections
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.ISocketClient.SocketCombineTarget">
<inheritdoc cref="P:CryptoExchange.Net.Objects.SocketClientOptions.SocketSubscriptionsCombineTarget"/>
</member>
<member name="P:CryptoExchange.Net.Interfaces.ISocketClient.MaxReconnectTries">
<inheritdoc cref="P:CryptoExchange.Net.Objects.SocketClientOptions.MaxReconnectTries"/>
</member>
<member name="P:CryptoExchange.Net.Interfaces.ISocketClient.MaxResubscribeTries">
<inheritdoc cref="P:CryptoExchange.Net.Objects.SocketClientOptions.MaxResubscribeTries"/>
</member>
<member name="P:CryptoExchange.Net.Interfaces.ISocketClient.MaxConcurrentResubscriptionsPerSocket">
<inheritdoc cref="P:CryptoExchange.Net.Objects.SocketClientOptions.MaxConcurrentResubscriptionsPerSocket"/>
</member>
<member name="P:CryptoExchange.Net.Interfaces.ISocketClient.IncomingKbps">
<summary>
The current kilobytes per second of data being received by all connection from this client, averaged over the last 3 seconds
Incoming kilobytes per second of data
</summary>
</member>
<member name="M:CryptoExchange.Net.Interfaces.ISocketClient.UnsubscribeAsync(CryptoExchange.Net.Sockets.UpdateSubscription)">
@ -2273,6 +2186,14 @@
If true, the CallResult and DataEvent objects will also include the originally received json data in the OriginalData property
</summary>
</member>
<member name="M:CryptoExchange.Net.Objects.BaseOptions.Copy``1(``0,``0)">
<summary>
Copy the values of the def to the input
</summary>
<typeparam name="T"></typeparam>
<param name="input"></param>
<param name="def"></param>
</member>
<member name="M:CryptoExchange.Net.Objects.BaseOptions.ToString">
<inheritdoc />
</member>
@ -2281,39 +2202,11 @@
Base for order book options
</summary>
</member>
<member name="P:CryptoExchange.Net.Objects.OrderBookOptions.OrderBookName">
<summary>
The name of the order book implementation
</summary>
</member>
<member name="P:CryptoExchange.Net.Objects.OrderBookOptions.ChecksumValidationEnabled">
<summary>
Whether or not checksum validation is enabled. Default is true, disabling will ignore checksum messages.
</summary>
</member>
<member name="P:CryptoExchange.Net.Objects.OrderBookOptions.SequenceNumbersAreConsecutive">
<summary>
Whether each update should have a consecutive id number. Used to identify and reconnect when numbers are skipped.
</summary>
</member>
<member name="P:CryptoExchange.Net.Objects.OrderBookOptions.StrictLevels">
<summary>
Whether or not a level should be removed from the book when it's pushed out of scope of the limit. For example with a book of limit 10,
when a new bid level is added which makes the total amount of bids 11, should the last bid entry be removed
</summary>
</member>
<member name="M:CryptoExchange.Net.Objects.OrderBookOptions.#ctor(System.String,System.Boolean,System.Boolean)">
<summary>
ctor
</summary>
<param name="name">The name of the order book implementation</param>
<param name="sequencesAreConsecutive">Whether each update should have a consecutive id number. Used to identify and reconnect when numbers are skipped.</param>
<param name="strictLevels">Whether or not a level should be removed from the book when it's pushed out of scope of the limit. For example with a book of limit 10,
when a new bid is added which makes the total amount of bids 11, should the last bid entry be removed</param>
</member>
<member name="M:CryptoExchange.Net.Objects.OrderBookOptions.ToString">
<inheritdoc />
</member>
<member name="T:CryptoExchange.Net.Objects.ClientOptions">
<summary>
Base client options
@ -2329,21 +2222,18 @@
The api credentials
</summary>
</member>
<member name="P:CryptoExchange.Net.Objects.ClientOptions.ShouldCheckObjects">
<summary>
Should check objects for missing properties based on the model and the received JSON
</summary>
</member>
<member name="P:CryptoExchange.Net.Objects.ClientOptions.Proxy">
<summary>
Proxy to use
</summary>
</member>
<member name="M:CryptoExchange.Net.Objects.ClientOptions.#ctor(System.String)">
<member name="M:CryptoExchange.Net.Objects.ClientOptions.Copy``1(``0,``0)">
<summary>
ctor
Copy the values of the def to the input
</summary>
<param name="baseAddress">The base address to use</param>
<typeparam name="T"></typeparam>
<param name="input"></param>
<param name="def"></param>
</member>
<member name="M:CryptoExchange.Net.Objects.ClientOptions.ToString">
<inheritdoc />
@ -2373,25 +2263,13 @@
Http client to use. If a HttpClient is provided in this property the RequestTimeout and Proxy options will be ignored in requests and should be set on the provided HttpClient instance
</summary>
</member>
<member name="M:CryptoExchange.Net.Objects.RestClientOptions.#ctor(System.String)">
<member name="M:CryptoExchange.Net.Objects.RestClientOptions.Copy``1(``0,``0)">
<summary>
ctor
</summary>
<param name="baseAddress">The base address of the API</param>
</member>
<member name="M:CryptoExchange.Net.Objects.RestClientOptions.#ctor(System.Net.Http.HttpClient,System.String)">
<summary>
ctor
</summary>
<param name="baseAddress">The base address of the API</param>
<param name="httpClient">Shared http client instance</param>
</member>
<member name="M:CryptoExchange.Net.Objects.RestClientOptions.Copy``1">
<summary>
Create a copy of the options
Copy the values of the def to the input
</summary>
<typeparam name="T"></typeparam>
<returns></returns>
<param name="input"></param>
<param name="def"></param>
</member>
<member name="M:CryptoExchange.Net.Objects.RestClientOptions.ToString">
<inheritdoc />
@ -2443,18 +2321,13 @@
single connection will also increase the amount of traffic on that single connection, potentially leading to issues.
</summary>
</member>
<member name="M:CryptoExchange.Net.Objects.SocketClientOptions.#ctor(System.String)">
<member name="M:CryptoExchange.Net.Objects.SocketClientOptions.Copy``1(``0,``0)">
<summary>
ctor
</summary>
<param name="baseAddress">The base address to use</param>
</member>
<member name="M:CryptoExchange.Net.Objects.SocketClientOptions.Copy``1">
<summary>
Create a copy of the options
Copy the values of the def to the input
</summary>
<typeparam name="T"></typeparam>
<returns></returns>
<param name="input"></param>
<param name="def"></param>
</member>
<member name="M:CryptoExchange.Net.Objects.SocketClientOptions.ToString">
<inheritdoc />
@ -2514,6 +2387,16 @@
The log
</summary>
</member>
<member name="F:CryptoExchange.Net.OrderBook.SymbolOrderBook.sequencesAreConsecutive">
<summary>
Whether update numbers are consecutive
</summary>
</member>
<member name="F:CryptoExchange.Net.OrderBook.SymbolOrderBook.strictLevels">
<summary>
Whether levels should be strictly enforced
</summary>
</member>
<member name="F:CryptoExchange.Net.OrderBook.SymbolOrderBook.bookSet">
<summary>
If order book is set
@ -2599,10 +2482,11 @@
BestBid/BesAsk returned as a pair
</summary>
</member>
<member name="M:CryptoExchange.Net.OrderBook.SymbolOrderBook.#ctor(System.String,CryptoExchange.Net.Objects.OrderBookOptions)">
<member name="M:CryptoExchange.Net.OrderBook.SymbolOrderBook.#ctor(System.String,System.String,CryptoExchange.Net.Objects.OrderBookOptions)">
<summary>
ctor
</summary>
<param name="id"></param>
<param name="symbol"></param>
<param name="options"></param>
</member>
@ -2936,16 +2820,6 @@
What request body should be set when no data is send (only used in combination with postParametersPosition.InBody)
</summary>
</member>
<member name="P:CryptoExchange.Net.RestClient.RequestTimeout">
<summary>
Timeout for requests. This setting is ignored when injecting a HttpClient in the options, requests timeouts should be set on the client then.
</summary>
</member>
<member name="P:CryptoExchange.Net.RestClient.RateLimitBehaviour">
<summary>
What should happen when running into a rate limit
</summary>
</member>
<member name="P:CryptoExchange.Net.RestClient.RateLimiters">
<summary>
List of rate limiters
@ -2961,6 +2835,11 @@
Request headers to be sent with each request
</summary>
</member>
<member name="P:CryptoExchange.Net.RestClient.ClientOptions">
<summary>
Client options
</summary>
</member>
<member name="M:CryptoExchange.Net.RestClient.#ctor(System.String,CryptoExchange.Net.Objects.RestClientOptions,CryptoExchange.Net.Authentication.AuthenticationProvider)">
<summary>
ctor
@ -2980,18 +2859,6 @@
Removes all rate limiters from this client
</summary>
</member>
<member name="M:CryptoExchange.Net.RestClient.Ping(System.Threading.CancellationToken)">
<summary>
Ping to see if the server is reachable
</summary>
<returns>The roundtrip time of the ping request</returns>
</member>
<member name="M:CryptoExchange.Net.RestClient.PingAsync(System.Threading.CancellationToken)">
<summary>
Ping to see if the server is reachable
</summary>
<returns>The roundtrip time of the ping request</returns>
</member>
<member name="M:CryptoExchange.Net.RestClient.SendRequestAsync``1(System.Uri,System.Net.Http.HttpMethod,System.Threading.CancellationToken,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean,System.Boolean,System.Nullable{CryptoExchange.Net.Objects.HttpMethodParameterPosition},System.Nullable{CryptoExchange.Net.Objects.ArrayParametersSerialization},System.Int32,Newtonsoft.Json.JsonSerializer,System.Collections.Generic.Dictionary{System.String,System.String})">
<summary>
Execute a request to the uri and deserialize the response into the provided type parameter
@ -3077,35 +2944,11 @@
Semaphore used while creating sockets
</summary>
</member>
<member name="P:CryptoExchange.Net.SocketClient.ReconnectInterval">
<inheritdoc cref="P:CryptoExchange.Net.Objects.SocketClientOptions.ReconnectInterval"/>
</member>
<member name="P:CryptoExchange.Net.SocketClient.AutoReconnect">
<inheritdoc cref="P:CryptoExchange.Net.Objects.SocketClientOptions.AutoReconnect"/>
</member>
<member name="P:CryptoExchange.Net.SocketClient.ResponseTimeout">
<inheritdoc cref="P:CryptoExchange.Net.Objects.SocketClientOptions.SocketResponseTimeout"/>
</member>
<member name="P:CryptoExchange.Net.SocketClient.SocketNoDataTimeout">
<inheritdoc cref="P:CryptoExchange.Net.Objects.SocketClientOptions.SocketNoDataTimeout"/>
</member>
<member name="P:CryptoExchange.Net.SocketClient.MaxSocketConnections">
<summary>
The max amount of concurrent socket connections
</summary>
</member>
<member name="P:CryptoExchange.Net.SocketClient.SocketCombineTarget">
<inheritdoc cref="P:CryptoExchange.Net.Objects.SocketClientOptions.SocketSubscriptionsCombineTarget"/>
</member>
<member name="P:CryptoExchange.Net.SocketClient.MaxReconnectTries">
<inheritdoc cref="P:CryptoExchange.Net.Objects.SocketClientOptions.MaxReconnectTries"/>
</member>
<member name="P:CryptoExchange.Net.SocketClient.MaxResubscribeTries">
<inheritdoc cref="P:CryptoExchange.Net.Objects.SocketClientOptions.MaxResubscribeTries"/>
</member>
<member name="P:CryptoExchange.Net.SocketClient.MaxConcurrentResubscriptionsPerSocket">
<inheritdoc cref="P:CryptoExchange.Net.Objects.SocketClientOptions.MaxConcurrentResubscriptionsPerSocket"/>
</member>
<member name="F:CryptoExchange.Net.SocketClient.dataInterpreterBytes">
<summary>
Delegate used for processing byte data received from socket connections before it is processed by handlers
@ -3157,6 +3000,11 @@
The current kilobytes per second of data being received by all connection from this client, averaged over the last 3 seconds
</summary>
</member>
<member name="P:CryptoExchange.Net.SocketClient.ClientOptions">
<summary>
Client options
</summary>
</member>
<member name="M:CryptoExchange.Net.SocketClient.#ctor(System.String,CryptoExchange.Net.Objects.SocketClientOptions,CryptoExchange.Net.Authentication.AuthenticationProvider)">
<summary>
ctor
@ -3312,7 +3160,7 @@
<param name="message"></param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.SocketClient.AddSubscription``1(System.Object,System.String,System.Boolean,CryptoExchange.Net.Sockets.SocketConnection,System.Action{CryptoExchange.Net.Sockets.DataEvent{``0}},System.Threading.CancellationToken)">
<member name="M:CryptoExchange.Net.SocketClient.AddSubscription``1(System.Object,System.String,System.Boolean,CryptoExchange.Net.Sockets.SocketConnection,System.Action{CryptoExchange.Net.Sockets.DataEvent{``0}})">
<summary>
Add a subscription to a connection
</summary>
@ -3943,7 +3791,6 @@
<param name="request"></param>
<param name="userSubscription"></param>
<param name="dataHandler"></param>
<param name="ct"></param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.Sockets.SocketSubscription.CreateForIdentifier(System.Int32,System.String,System.Boolean,System.Action{CryptoExchange.Net.Sockets.MessageEvent})">
@ -3954,7 +3801,6 @@
<param name="identifier"></param>
<param name="userSubscription"></param>
<param name="dataHandler"></param>
<param name="ct"></param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.Sockets.SocketSubscription.InvokeExceptionHandler(System.Exception)">
@ -4053,5 +3899,148 @@
<member name="M:CryptoExchange.Net.Sockets.WebsocketFactory.CreateWebsocket(CryptoExchange.Net.Logging.Log,System.String,System.Collections.Generic.IDictionary{System.String,System.String},System.Collections.Generic.IDictionary{System.String,System.String})">
<inheritdoc />
</member>
<member name="T:System.Diagnostics.CodeAnalysis.AllowNullAttribute">
<summary>
Specifies that <see langword="null"/> is allowed as an input even if the
corresponding type disallows it.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.AllowNullAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.AllowNullAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.DisallowNullAttribute">
<summary>
Specifies that <see langword="null"/> is disallowed as an input even if the
corresponding type allows it.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.DisallowNullAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.DisallowNullAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute">
<summary>
Specifies that a method that will never return under any circumstance.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute">
<summary>
Specifies that the method will not return if the associated <see cref="T:System.Boolean"/>
parameter is passed the specified value.
</summary>
</member>
<member name="P:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute.ParameterValue">
<summary>
Gets the condition parameter value.
Code after the method is considered unreachable by diagnostics if the argument
to the associated parameter matches this value.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute.#ctor(System.Boolean)">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute"/>
class with the specified parameter value.
</summary>
<param name="parameterValue">
The condition parameter value.
Code after the method is considered unreachable by diagnostics if the argument
to the associated parameter matches this value.
</param>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.MaybeNullAttribute">
<summary>
Specifies that an output may be <see langword="null"/> even if the
corresponding type disallows it.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.MaybeNullAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.MaybeNullAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute">
<summary>
Specifies that when a method returns <see cref="P:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute.ReturnValue"/>,
the parameter may be <see langword="null"/> even if the corresponding type disallows it.
</summary>
</member>
<member name="P:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute.ReturnValue">
<summary>
Gets the return value condition.
If the method returns this value, the associated parameter may be <see langword="null"/>.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute.#ctor(System.Boolean)">
<summary>
Initializes the attribute with the specified return value condition.
</summary>
<param name="returnValue">
The return value condition.
If the method returns this value, the associated parameter may be <see langword="null"/>.
</param>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.NotNullAttribute">
<summary>
Specifies that an output is not <see langword="null"/> even if the
corresponding type allows it.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.NotNullAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.NotNullAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute">
<summary>
Specifies that the output will be non-<see langword="null"/> if the
named parameter is non-<see langword="null"/>.
</summary>
</member>
<member name="P:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute.ParameterName">
<summary>
Gets the associated parameter name.
The output will be non-<see langword="null"/> if the argument to the
parameter specified is non-<see langword="null"/>.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute.#ctor(System.String)">
<summary>
Initializes the attribute with the associated parameter name.
</summary>
<param name="parameterName">
The associated parameter name.
The output will be non-<see langword="null"/> if the argument to the
parameter specified is non-<see langword="null"/>.
</param>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute">
<summary>
Specifies that when a method returns <see cref="P:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute.ReturnValue"/>,
the parameter will not be <see langword="null"/> even if the corresponding type allows it.
</summary>
</member>
<member name="P:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute.ReturnValue">
<summary>
Gets the return value condition.
If the method returns this value, the associated parameter will not be <see langword="null"/>.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute.#ctor(System.Boolean)">
<summary>
Initializes the attribute with the specified return value condition.
</summary>
<param name="returnValue">
The return value condition.
If the method returns this value, the associated parameter will not be <see langword="null"/>.
</param>
</member>
</members>
</doc>

View File

@ -17,31 +17,11 @@ namespace CryptoExchange.Net.Interfaces
/// </summary>
IRequestFactory RequestFactory { get; set; }
/// <summary>
/// What should happen when hitting a rate limit
/// </summary>
RateLimitingBehaviour RateLimitBehaviour { get; }
/// <summary>
/// List of active rate limiters
/// </summary>
IEnumerable<IRateLimiter> RateLimiters { get; }
/// <summary>
/// The total amount of requests made
/// </summary>
int TotalRequestsMade { get; }
/// <summary>
/// The base address of the API
/// </summary>
string BaseAddress { get; }
/// <summary>
/// Client name
/// </summary>
string ExchangeName { get; }
/// <summary>
/// Adds a rate limiter to the client. There are 2 choices, the <see cref="RateLimiterTotal"/> and the <see cref="RateLimiterPerEndpoint"/>.
/// </summary>
@ -54,15 +34,8 @@ namespace CryptoExchange.Net.Interfaces
void RemoveRateLimiters();
/// <summary>
/// Ping to see if the server is reachable
/// Client options
/// </summary>
/// <returns>The roundtrip time of the ping request</returns>
CallResult<long> Ping(CancellationToken ct = default);
/// <summary>
/// Ping to see if the server is reachable
/// </summary>
/// <returns>The roundtrip time of the ping request</returns>
Task<CallResult<long>> PingAsync(CancellationToken ct = default);
RestClientOptions ClientOptions { get; }
}
}

View File

@ -11,48 +11,14 @@ namespace CryptoExchange.Net.Interfaces
public interface ISocketClient: IDisposable
{
/// <summary>
/// The factory for creating sockets. Used for unit testing
/// Client options
/// </summary>
IWebsocketFactory SocketFactory { get; set; }
SocketClientOptions ClientOptions { get; }
/// <summary>
/// The time in between reconnect attempts
/// Incoming kilobytes per second of data
/// </summary>
TimeSpan ReconnectInterval { get; }
/// <summary>
/// Whether the client should try to auto reconnect when losing connection
/// </summary>
bool AutoReconnect { get; }
/// <summary>
/// The base address of the API
/// </summary>
string BaseAddress { get; }
/// <inheritdoc cref="SocketClientOptions.SocketResponseTimeout"/>
TimeSpan ResponseTimeout { get; }
/// <inheritdoc cref="SocketClientOptions.SocketNoDataTimeout"/>
TimeSpan SocketNoDataTimeout { get; }
/// <summary>
/// The max amount of concurrent socket connections
/// </summary>
int MaxSocketConnections { get; }
/// <inheritdoc cref="SocketClientOptions.SocketSubscriptionsCombineTarget"/>
int SocketCombineTarget { get; }
/// <inheritdoc cref="SocketClientOptions.MaxReconnectTries"/>
int? MaxReconnectTries { get; }
/// <inheritdoc cref="SocketClientOptions.MaxResubscribeTries"/>
int? MaxResubscribeTries { get; }
/// <inheritdoc cref="SocketClientOptions.MaxConcurrentResubscriptionsPerSocket"/>
int MaxConcurrentResubscriptionsPerSocket { get; }
/// <summary>
/// The current kilobytes per second of data being received by all connection from this client, averaged over the last 3 seconds
/// </summary>
double IncomingKbps { get; }
public double IncomingKbps { get; }
/// <summary>
/// Unsubscribe from a stream

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Interfaces;
@ -16,7 +17,7 @@ namespace CryptoExchange.Net.Objects
/// <summary>
/// The minimum log level to output. Setting it to null will send all messages to the registered ILoggers.
/// </summary>
public LogLevel? LogLevel { get; set; } = Microsoft.Extensions.Logging.LogLevel.Information;
public LogLevel LogLevel { get; set; } = LogLevel.Information;
/// <summary>
/// The log writers
@ -28,6 +29,19 @@ namespace CryptoExchange.Net.Objects
/// </summary>
public bool OutputOriginalData { get; set; } = false;
/// <summary>
/// Copy the values of the def to the input
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="input"></param>
/// <param name="def"></param>
public void Copy<T>(T input, T def) where T : BaseOptions
{
input.LogLevel = def.LogLevel;
input.LogWriters = def.LogWriters.ToList();
input.OutputOriginalData = def.OutputOriginalData;
}
/// <inheritdoc />
public override string ToString()
{
@ -39,47 +53,11 @@ namespace CryptoExchange.Net.Objects
/// Base for order book options
/// </summary>
public class OrderBookOptions : BaseOptions
{
/// <summary>
/// The name of the order book implementation
/// </summary>
public string OrderBookName { get; }
{
/// <summary>
/// Whether or not checksum validation is enabled. Default is true, disabling will ignore checksum messages.
/// </summary>
public bool ChecksumValidationEnabled { get; set; } = true;
/// <summary>
/// Whether each update should have a consecutive id number. Used to identify and reconnect when numbers are skipped.
/// </summary>
public bool SequenceNumbersAreConsecutive { get; }
/// <summary>
/// Whether or not a level should be removed from the book when it's pushed out of scope of the limit. For example with a book of limit 10,
/// when a new bid level is added which makes the total amount of bids 11, should the last bid entry be removed
/// </summary>
public bool StrictLevels { get; }
/// <summary>
/// ctor
/// </summary>
/// <param name="name">The name of the order book implementation</param>
/// <param name="sequencesAreConsecutive">Whether each update should have a consecutive id number. Used to identify and reconnect when numbers are skipped.</param>
/// <param name="strictLevels">Whether or not a level should be removed from the book when it's pushed out of scope of the limit. For example with a book of limit 10,
/// when a new bid is added which makes the total amount of bids 11, should the last bid entry be removed</param>
public OrderBookOptions(string name, bool sequencesAreConsecutive, bool strictLevels)
{
OrderBookName = name;
SequenceNumbersAreConsecutive = sequencesAreConsecutive;
StrictLevels = strictLevels;
}
/// <inheritdoc />
public override string ToString()
{
return $"{base.ToString()}, OrderBookName: {OrderBookName}, SequenceNumbersAreConsequtive: {SequenceNumbersAreConsecutive}, StrictLevels: {StrictLevels}";
}
}
/// <summary>
@ -87,7 +65,7 @@ namespace CryptoExchange.Net.Objects
/// </summary>
public class ClientOptions : BaseOptions
{
private string _baseAddress;
private string _baseAddress = string.Empty;
/// <summary>
/// The base address of the client
@ -97,6 +75,9 @@ namespace CryptoExchange.Net.Objects
get => _baseAddress;
set
{
if (value == null)
return;
var newValue = value;
if (!newValue.EndsWith("/"))
newValue += "/";
@ -109,25 +90,24 @@ namespace CryptoExchange.Net.Objects
/// </summary>
public ApiCredentials? ApiCredentials { get; set; }
/// <summary>
/// Should check objects for missing properties based on the model and the received JSON
/// </summary>
public bool ShouldCheckObjects { get; set; } = false;
/// <summary>
/// Proxy to use
/// </summary>
public ApiProxy? Proxy { get; set; }
/// <summary>
/// ctor
/// Copy the values of the def to the input
/// </summary>
/// <param name="baseAddress">The base address to use</param>
#pragma warning disable 8618
public ClientOptions(string baseAddress)
#pragma warning restore 8618
/// <typeparam name="T"></typeparam>
/// <param name="input"></param>
/// <param name="def"></param>
public new void Copy<T>(T input, T def) where T : ClientOptions
{
BaseAddress = baseAddress;
base.Copy(input, def);
input.BaseAddress = def.BaseAddress;
input.ApiCredentials = def.ApiCredentials?.Copy();
input.Proxy = def.Proxy;
}
/// <inheritdoc />
@ -163,44 +143,19 @@ namespace CryptoExchange.Net.Objects
public HttpClient? HttpClient { get; set; }
/// <summary>
/// ctor
/// </summary>
/// <param name="baseAddress">The base address of the API</param>
public RestClientOptions(string baseAddress): base(baseAddress)
{
}
/// <summary>
/// ctor
/// </summary>
/// <param name="baseAddress">The base address of the API</param>
/// <param name="httpClient">Shared http client instance</param>
public RestClientOptions(HttpClient httpClient, string baseAddress) : base(baseAddress)
{
HttpClient = httpClient;
}
/// <summary>
/// Create a copy of the options
/// Copy the values of the def to the input
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T Copy<T>() where T : RestClientOptions, new()
/// <param name="input"></param>
/// <param name="def"></param>
public new void Copy<T>(T input, T def) where T : RestClientOptions
{
var copy = new T
{
BaseAddress = BaseAddress,
LogLevel = LogLevel,
Proxy = Proxy,
LogWriters = LogWriters,
RateLimiters = RateLimiters,
RateLimitingBehaviour = RateLimitingBehaviour,
RequestTimeout = RequestTimeout,
HttpClient = HttpClient
};
if (ApiCredentials != null)
copy.ApiCredentials = ApiCredentials.Copy();
return copy;
base.Copy(input, def);
input.HttpClient = def.HttpClient;
input.RateLimiters = def.RateLimiters.ToList();
input.RateLimitingBehaviour = def.RateLimitingBehaviour;
input.RequestTimeout = def.RequestTimeout;
}
/// <inheritdoc />
@ -257,36 +212,23 @@ namespace CryptoExchange.Net.Objects
public int? SocketSubscriptionsCombineTarget { get; set; }
/// <summary>
/// ctor
/// </summary>
/// <param name="baseAddress">The base address to use</param>
public SocketClientOptions(string baseAddress) : base(baseAddress)
{
}
/// <summary>
/// Create a copy of the options
/// Copy the values of the def to the input
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T Copy<T>() where T : SocketClientOptions, new()
/// <param name="input"></param>
/// <param name="def"></param>
public new void Copy<T>(T input, T def) where T : SocketClientOptions
{
var copy = new T
{
BaseAddress = BaseAddress,
LogLevel = LogLevel,
Proxy = Proxy,
LogWriters = LogWriters,
AutoReconnect = AutoReconnect,
ReconnectInterval = ReconnectInterval,
SocketResponseTimeout = SocketResponseTimeout,
SocketSubscriptionsCombineTarget = SocketSubscriptionsCombineTarget
};
base.Copy(input, def);
if (ApiCredentials != null)
copy.ApiCredentials = ApiCredentials.Copy();
return copy;
input.AutoReconnect = def.AutoReconnect;
input.ReconnectInterval = def.ReconnectInterval;
input.MaxReconnectTries = def.MaxReconnectTries;
input.MaxResubscribeTries = def.MaxResubscribeTries;
input.MaxConcurrentResubscriptionsPerSocket = def.MaxConcurrentResubscriptionsPerSocket;
input.SocketResponseTimeout = def.SocketResponseTimeout;
input.SocketNoDataTimeout = def.SocketNoDataTimeout;
input.SocketSubscriptionsCombineTarget = def.SocketSubscriptionsCombineTarget;
}
/// <inheritdoc />

View File

@ -35,8 +35,7 @@ namespace CryptoExchange.Net.OrderBook
private OrderBookStatus status;
private UpdateSubscription? subscription;
private readonly bool sequencesAreConsecutive;
private readonly bool strictLevels;
private readonly bool validateChecksum;
private bool _stopProcessing;
@ -53,6 +52,16 @@ namespace CryptoExchange.Net.OrderBook
/// </summary>
protected Log log;
/// <summary>
/// Whether update numbers are consecutive
/// </summary>
protected bool sequencesAreConsecutive;
/// <summary>
/// Whether levels should be strictly enforced
/// </summary>
protected bool strictLevels;
/// <summary>
/// If order book is set
/// </summary>
@ -199,9 +208,10 @@ namespace CryptoExchange.Net.OrderBook
/// <summary>
/// ctor
/// </summary>
/// <param name="id"></param>
/// <param name="symbol"></param>
/// <param name="options"></param>
protected SymbolOrderBook(string symbol, OrderBookOptions options)
protected SymbolOrderBook(string id, string symbol, OrderBookOptions options)
{
if (symbol == null)
throw new ArgumentNullException(nameof(symbol));
@ -209,13 +219,11 @@ namespace CryptoExchange.Net.OrderBook
if (options == null)
throw new ArgumentNullException(nameof(options));
Id = options.OrderBookName;
Id = id;
processBuffer = new List<ProcessBufferRangeSequenceEntry>();
_processQueue = new ConcurrentQueue<object>();
_queueEvent = new AutoResetEvent(false);
sequencesAreConsecutive = options.SequenceNumbersAreConsecutive;
strictLevels = options.StrictLevels;
validateChecksum = options.ChecksumValidationEnabled;
Symbol = symbol;
Status = OrderBookStatus.Disconnected;
@ -223,7 +231,7 @@ namespace CryptoExchange.Net.OrderBook
asks = new SortedList<decimal, ISymbolOrderBookEntry>();
bids = new SortedList<decimal, ISymbolOrderBookEntry>(new DescComparer<decimal>());
log = new Log(options.OrderBookName) { Level = options.LogLevel };
log = new Log(id) { Level = options.LogLevel };
var writers = options.LogWriters ?? new List<ILogger> { new DebugLogger() };
log.UpdateWriters(writers.ToList());
}

View File

@ -61,19 +61,12 @@ namespace CryptoExchange.Net
/// What request body should be set when no data is send (only used in combination with postParametersPosition.InBody)
/// </summary>
protected string requestBodyEmptyContent = "{}";
/// <summary>
/// Timeout for requests. This setting is ignored when injecting a HttpClient in the options, requests timeouts should be set on the client then.
/// </summary>
public TimeSpan RequestTimeout { get; }
/// <summary>
/// What should happen when running into a rate limit
/// </summary>
public RateLimitingBehaviour RateLimitBehaviour { get; }
/// <summary>
/// List of rate limiters
/// </summary>
public IEnumerable<IRateLimiter> RateLimiters { get; private set; }
protected IEnumerable<IRateLimiter> RateLimiters { get; private set; }
/// <summary>
/// Total requests made by this client
/// </summary>
@ -84,6 +77,11 @@ namespace CryptoExchange.Net
/// </summary>
protected Dictionary<string, string>? StandardRequestHeaders { get; set; }
/// <summary>
/// Client options
/// </summary>
public new RestClientOptions ClientOptions { get; }
/// <summary>
/// ctor
/// </summary>
@ -95,9 +93,9 @@ namespace CryptoExchange.Net
if (exchangeOptions == null)
throw new ArgumentNullException(nameof(exchangeOptions));
RequestTimeout = exchangeOptions.RequestTimeout;
ClientOptions = exchangeOptions;
RequestFactory.Configure(exchangeOptions.RequestTimeout, exchangeOptions.Proxy, exchangeOptions.HttpClient);
RateLimitBehaviour = exchangeOptions.RateLimitingBehaviour;
var rateLimiters = new List<IRateLimiter>();
foreach (var rateLimiter in exchangeOptions.RateLimiters)
rateLimiters.Add(rateLimiter);
@ -126,48 +124,6 @@ namespace CryptoExchange.Net
RateLimiters = new List<IRateLimiter>();
}
/// <summary>
/// Ping to see if the server is reachable
/// </summary>
/// <returns>The roundtrip time of the ping request</returns>
public virtual CallResult<long> Ping(CancellationToken ct = default) => PingAsync(ct).Result;
/// <summary>
/// Ping to see if the server is reachable
/// </summary>
/// <returns>The roundtrip time of the ping request</returns>
public virtual async Task<CallResult<long>> PingAsync(CancellationToken ct = default)
{
var ping = new Ping();
var uri = new Uri(BaseAddress);
PingReply reply;
var ctRegistration = ct.Register(() => ping.SendAsyncCancel());
try
{
reply = await ping.SendPingAsync(uri.Host).ConfigureAwait(false);
}
catch (PingException e)
{
if (e.InnerException == null)
return new CallResult<long>(0, new CantConnectError { Message = "Ping failed: " + e.Message });
if (e.InnerException is SocketException exception)
return new CallResult<long>(0, new CantConnectError { Message = "Ping failed: " + exception.SocketErrorCode });
return new CallResult<long>(0, new CantConnectError { Message = "Ping failed: " + e.InnerException.Message });
}
finally
{
ctRegistration.Dispose();
ping.Dispose();
}
if (ct.IsCancellationRequested)
return new CallResult<long>(0, new CancellationRequestedError());
return reply.Status == IPStatus.Success ? new CallResult<long>(reply.RoundtripTime, null) : new CallResult<long>(0, new CantConnectError { Message = "Ping failed: " + reply.Status });
}
/// <summary>
/// Execute a request to the uri and deserialize the response into the provided type parameter
/// </summary>
@ -210,7 +166,7 @@ namespace CryptoExchange.Net
var request = ConstructRequest(uri, method, parameters, signed, paramsPosition, arraySerialization ?? this.arraySerialization, requestId, additionalHeaders);
foreach (var limiter in RateLimiters)
{
var limitResult = limiter.LimitRequest(this, uri.AbsolutePath, RateLimitBehaviour, credits);
var limitResult = limiter.LimitRequest(this, uri.AbsolutePath, ClientOptions.RateLimitingBehaviour, credits);
if (!limitResult.Success)
{
log.Write(LogLevel.Information, $"[{requestId}] Request {uri.AbsolutePath} failed because of rate limit");
@ -232,7 +188,7 @@ namespace CryptoExchange.Net
paramString += " with headers " + string.Join(", ", headers.Select(h => h.Key + $"=[{string.Join(",", h.Value)}]"));
}
log.Write(LogLevel.Debug, $"[{requestId}] Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}{(apiProxy == null ? "" : $" via proxy {apiProxy.Host}")}");
log.Write(LogLevel.Debug, $"[{requestId}] Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}{(ClientOptions.Proxy == null ? "" : $" via proxy {ClientOptions.Proxy.Host}")}");
return await GetResponseAsync<T>(request, deserializer, cancellationToken).ConfigureAwait(false);
}
@ -277,8 +233,8 @@ namespace CryptoExchange.Net
return WebCallResult<T>.CreateErrorResult(response.StatusCode, response.ResponseHeaders, error);
// Not an error, so continue deserializing
var deserializeResult = Deserialize<T>(parseResult.Data, null, deserializer, request.RequestId);
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, OutputOriginalData ? data: null, deserializeResult.Data, deserializeResult.Error);
var deserializeResult = Deserialize<T>(parseResult.Data, deserializer, request.RequestId);
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, ClientOptions.OutputOriginalData ? data: null, deserializeResult.Data, deserializeResult.Error);
}
else
{
@ -287,7 +243,7 @@ namespace CryptoExchange.Net
responseStream.Close();
response.Close();
return new WebCallResult<T>(statusCode, headers, OutputOriginalData ? desResult.OriginalData : null, desResult.Data, desResult.Error);
return new WebCallResult<T>(statusCode, headers, ClientOptions.OutputOriginalData ? desResult.OriginalData : null, desResult.Data, desResult.Error);
}
}
else

View File

@ -35,27 +35,10 @@ namespace CryptoExchange.Net
/// Semaphore used while creating sockets
/// </summary>
protected internal readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1);
/// <inheritdoc cref="SocketClientOptions.ReconnectInterval"/>
public TimeSpan ReconnectInterval { get; }
/// <inheritdoc cref="SocketClientOptions.AutoReconnect"/>
public bool AutoReconnect { get; }
/// <inheritdoc cref="SocketClientOptions.SocketResponseTimeout"/>
public TimeSpan ResponseTimeout { get; }
/// <inheritdoc cref="SocketClientOptions.SocketNoDataTimeout"/>
public TimeSpan SocketNoDataTimeout { get; }
/// <summary>
/// The max amount of concurrent socket connections
/// </summary>
public int MaxSocketConnections { get; protected set; } = 9999;
/// <inheritdoc cref="SocketClientOptions.SocketSubscriptionsCombineTarget"/>
public int SocketCombineTarget { get; protected set; }
/// <inheritdoc cref="SocketClientOptions.MaxReconnectTries"/>
public int? MaxReconnectTries { get; protected set; }
/// <inheritdoc cref="SocketClientOptions.MaxResubscribeTries"/>
public int? MaxResubscribeTries { get; protected set; }
/// <inheritdoc cref="SocketClientOptions.MaxConcurrentResubscriptionsPerSocket"/>
public int MaxConcurrentResubscriptionsPerSocket { get; protected set; }
protected int MaxSocketConnections { get; set; } = 9999;
/// <summary>
/// Delegate used for processing byte data received from socket connections before it is processed by handlers
/// </summary>
@ -110,6 +93,12 @@ namespace CryptoExchange.Net
return sockets.Sum(s => s.Value.Socket.IncomingKbps);
}
}
/// <summary>
/// Client options
/// </summary>
public new SocketClientOptions ClientOptions { get; }
#endregion
/// <summary>
@ -123,14 +112,7 @@ namespace CryptoExchange.Net
if (exchangeOptions == null)
throw new ArgumentNullException(nameof(exchangeOptions));
AutoReconnect = exchangeOptions.AutoReconnect;
ReconnectInterval = exchangeOptions.ReconnectInterval;
ResponseTimeout = exchangeOptions.SocketResponseTimeout;
SocketNoDataTimeout = exchangeOptions.SocketNoDataTimeout;
SocketCombineTarget = exchangeOptions.SocketSubscriptionsCombineTarget ?? 1;
MaxReconnectTries = exchangeOptions.MaxReconnectTries;
MaxResubscribeTries = exchangeOptions.MaxResubscribeTries;
MaxConcurrentResubscriptionsPerSocket = exchangeOptions.MaxConcurrentResubscriptionsPerSocket;
ClientOptions = exchangeOptions;
}
/// <summary>
@ -156,7 +138,7 @@ namespace CryptoExchange.Net
/// <returns></returns>
protected virtual Task<CallResult<UpdateSubscription>> SubscribeAsync<T>(object? request, string? identifier, bool authenticated, Action<DataEvent<T>> dataHandler, CancellationToken ct)
{
return SubscribeAsync(BaseAddress, request, identifier, authenticated, dataHandler, ct);
return SubscribeAsync(ClientOptions.BaseAddress, request, identifier, authenticated, dataHandler, ct);
}
/// <summary>
@ -184,8 +166,8 @@ namespace CryptoExchange.Net
socketConnection = GetSocketConnection(url, authenticated);
// Add a subscription on the socket connection
subscription = AddSubscription(request, identifier, true, socketConnection, dataHandler, ct);
if (SocketCombineTarget == 1)
subscription = AddSubscription(request, identifier, true, socketConnection, dataHandler);
if (ClientOptions.SocketSubscriptionsCombineTarget == 1)
{
// Only 1 subscription per connection, so no need to wait for connection since a new subscription will create a new connection anyway
semaphoreSlim.Release();
@ -251,7 +233,7 @@ namespace CryptoExchange.Net
protected internal virtual async Task<CallResult<bool>> SubscribeAndWaitAsync(SocketConnection socketConnection, object request, SocketSubscription subscription)
{
CallResult<object>? callResult = null;
await socketConnection.SendAndWaitAsync(request, ResponseTimeout, data => HandleSubscriptionResponse(socketConnection, subscription, request, data, out callResult)).ConfigureAwait(false);
await socketConnection.SendAndWaitAsync(request, ClientOptions.SocketResponseTimeout, data => HandleSubscriptionResponse(socketConnection, subscription, request, data, out callResult)).ConfigureAwait(false);
if (callResult?.Success == true)
subscription.Confirmed = true;
@ -268,7 +250,7 @@ namespace CryptoExchange.Net
/// <returns></returns>
protected virtual Task<CallResult<T>> QueryAsync<T>(object request, bool authenticated)
{
return QueryAsync<T>(BaseAddress, request, authenticated);
return QueryAsync<T>(ClientOptions.BaseAddress, request, authenticated);
}
/// <summary>
@ -287,7 +269,7 @@ namespace CryptoExchange.Net
try
{
socketConnection = GetSocketConnection(url, authenticated);
if (SocketCombineTarget == 1)
if (ClientOptions.SocketSubscriptionsCombineTarget == 1)
{
// Can release early when only a single sub per connection
semaphoreSlim.Release();
@ -325,7 +307,7 @@ namespace CryptoExchange.Net
protected virtual async Task<CallResult<T>> QueryAndWaitAsync<T>(SocketConnection socket, object request)
{
var dataResult = new CallResult<T>(default, new ServerError("No response on query received"));
await socket.SendAndWaitAsync(request, ResponseTimeout, data =>
await socket.SendAndWaitAsync(request, ClientOptions.SocketResponseTimeout, data =>
{
if (!HandleQueryResponse<T>(socket, request, data, out var callResult))
return false;
@ -445,25 +427,25 @@ namespace CryptoExchange.Net
/// <param name="connection">The socket connection the handler is on</param>
/// <param name="dataHandler">The handler of the data received</param>
/// <returns></returns>
protected virtual SocketSubscription AddSubscription<T>(object? request, string? identifier, bool userSubscription, SocketConnection connection, Action<DataEvent<T>> dataHandler, CancellationToken ct)
protected virtual SocketSubscription AddSubscription<T>(object? request, string? identifier, bool userSubscription, SocketConnection connection, Action<DataEvent<T>> dataHandler)
{
void InternalHandler(MessageEvent messageEvent)
{
if (typeof(T) == typeof(string))
{
var stringData = (T)Convert.ChangeType(messageEvent.JsonData.ToString(), typeof(T));
dataHandler(new DataEvent<T>(stringData, null, OutputOriginalData ? messageEvent.OriginalData : null, messageEvent.ReceivedTimestamp));
dataHandler(new DataEvent<T>(stringData, null, ClientOptions.OutputOriginalData ? messageEvent.OriginalData : null, messageEvent.ReceivedTimestamp));
return;
}
var desResult = Deserialize<T>(messageEvent.JsonData, false);
var desResult = Deserialize<T>(messageEvent.JsonData);
if (!desResult)
{
log.Write(LogLevel.Warning, $"Socket {connection.Socket.Id} Failed to deserialize data into type {typeof(T)}: {desResult.Error}");
return;
}
dataHandler(new DataEvent<T>(desResult.Data, null, OutputOriginalData ? messageEvent.OriginalData : null, messageEvent.ReceivedTimestamp));
dataHandler(new DataEvent<T>(desResult.Data, null, ClientOptions.OutputOriginalData ? messageEvent.OriginalData : null, messageEvent.ReceivedTimestamp));
}
var subscription = request == null
@ -499,7 +481,7 @@ namespace CryptoExchange.Net
var result = socketResult.Equals(default(KeyValuePair<int, SocketConnection>)) ? null : socketResult.Value;
if (result != null)
{
if (result.SubscriptionCount < SocketCombineTarget || (sockets.Count >= MaxSocketConnections && sockets.All(s => s.Value.SubscriptionCount >= SocketCombineTarget)))
if (result.SubscriptionCount < ClientOptions.SocketSubscriptionsCombineTarget || (sockets.Count >= MaxSocketConnections && sockets.All(s => s.Value.SubscriptionCount >= ClientOptions.SocketSubscriptionsCombineTarget)))
{
// Use existing socket if it has less than target connections OR it has the least connections and we can't make new
return result;
@ -554,10 +536,10 @@ namespace CryptoExchange.Net
var socket = SocketFactory.CreateWebsocket(log, address);
log.Write(LogLevel.Debug, $"Socket {socket.Id} new socket created for " + address);
if (apiProxy != null)
socket.SetProxy(apiProxy);
if (ClientOptions.Proxy != null)
socket.SetProxy(ClientOptions.Proxy);
socket.Timeout = SocketNoDataTimeout;
socket.Timeout = ClientOptions.SocketNoDataTimeout;
socket.DataInterpreterBytes = dataInterpreterBytes;
socket.DataInterpreterString = dataInterpreterString;
socket.RatelimitPerSecond = RateLimitPerSocketPerSecond;

View File

@ -129,7 +129,7 @@ namespace CryptoExchange.Net.Sockets
subscriptions = new List<SocketSubscription>();
Socket = socket;
Socket.Timeout = client.SocketNoDataTimeout;
Socket.Timeout = client.ClientOptions.SocketNoDataTimeout;
Socket.OnMessage += ProcessMessage;
Socket.OnClose += SocketOnClose;
Socket.OnOpen += SocketOnOpen;
@ -183,7 +183,7 @@ namespace CryptoExchange.Net.Sockets
}
// Message was not a request response, check data handlers
var messageEvent = new MessageEvent(this, tokenData, socketClient.OutputOriginalData ? data: null, timestamp);
var messageEvent = new MessageEvent(this, tokenData, socketClient.ClientOptions.OutputOriginalData ? data: null, timestamp);
if (!HandleData(messageEvent) && !handledResponse)
{
if (!socketClient.UnhandledMessageExpected)
@ -330,7 +330,7 @@ namespace CryptoExchange.Net.Sockets
}
}
if (socketClient.AutoReconnect && ShouldReconnect)
if (socketClient.ClientOptions.AutoReconnect && ShouldReconnect)
{
if (Socket.Reconnecting)
return; // Already reconnecting
@ -338,7 +338,7 @@ namespace CryptoExchange.Net.Sockets
Socket.Reconnecting = true;
DisconnectTime = DateTime.UtcNow;
log.Write(LogLevel.Information, $"Socket {Socket.Id} Connection lost, will try to reconnect after {socketClient.ReconnectInterval}");
log.Write(LogLevel.Information, $"Socket {Socket.Id} Connection lost, will try to reconnect after {socketClient.ClientOptions.ReconnectInterval}");
if (!lostTriggered)
{
lostTriggered = true;
@ -350,7 +350,7 @@ namespace CryptoExchange.Net.Sockets
while (ShouldReconnect)
{
// Wait a bit before attempting reconnect
await Task.Delay(socketClient.ReconnectInterval).ConfigureAwait(false);
await Task.Delay(socketClient.ClientOptions.ReconnectInterval).ConfigureAwait(false);
if (!ShouldReconnect)
{
// Should reconnect changed to false while waiting to reconnect
@ -363,8 +363,8 @@ namespace CryptoExchange.Net.Sockets
{
ReconnectTry++;
ResubscribeTry = 0;
if (socketClient.MaxReconnectTries != null
&& ReconnectTry >= socketClient.MaxReconnectTries)
if (socketClient.ClientOptions.MaxReconnectTries != null
&& ReconnectTry >= socketClient.ClientOptions.MaxReconnectTries)
{
log.Write(LogLevel.Debug, $"Socket {Socket.Id} failed to reconnect after {ReconnectTry} tries, closing");
ShouldReconnect = false;
@ -377,7 +377,7 @@ namespace CryptoExchange.Net.Sockets
break;
}
log.Write(LogLevel.Debug, $"Socket {Socket.Id} failed to reconnect{(socketClient.MaxReconnectTries != null ? $", try {ReconnectTry}/{socketClient.MaxReconnectTries}": "")}");
log.Write(LogLevel.Debug, $"Socket {Socket.Id} failed to reconnect{(socketClient.ClientOptions.MaxReconnectTries != null ? $", try {ReconnectTry}/{socketClient.ClientOptions.MaxReconnectTries}": "")}");
continue;
}
@ -392,8 +392,8 @@ namespace CryptoExchange.Net.Sockets
{
ResubscribeTry++;
if (socketClient.MaxResubscribeTries != null &&
ResubscribeTry >= socketClient.MaxResubscribeTries)
if (socketClient.ClientOptions.MaxResubscribeTries != null &&
ResubscribeTry >= socketClient.ClientOptions.MaxResubscribeTries)
{
log.Write(LogLevel.Debug, $"Socket {Socket.Id} failed to resubscribe after {ResubscribeTry} tries, closing");
ShouldReconnect = false;
@ -405,7 +405,7 @@ namespace CryptoExchange.Net.Sockets
_ = Task.Run(() => ConnectionClosed?.Invoke());
}
else
log.Write(LogLevel.Debug, $"Socket {Socket.Id} resubscribing all subscriptions failed on reconnected socket{(socketClient.MaxResubscribeTries != null ? $", try {ResubscribeTry}/{socketClient.MaxResubscribeTries}" : "")}. Disconnecting and reconnecting.");
log.Write(LogLevel.Debug, $"Socket {Socket.Id} resubscribing all subscriptions failed on reconnected socket{(socketClient.ClientOptions.MaxResubscribeTries != null ? $", try {ResubscribeTry}/{socketClient.ClientOptions.MaxResubscribeTries}" : "")}. Disconnecting and reconnecting.");
if (Socket.IsOpen)
await Socket.CloseAsync().ConfigureAwait(false);
@ -431,7 +431,7 @@ namespace CryptoExchange.Net.Sockets
}
else
{
if (!socketClient.AutoReconnect && ShouldReconnect)
if (!socketClient.ClientOptions.AutoReconnect && ShouldReconnect)
_ = Task.Run(() => ConnectionClosed?.Invoke());
// No reconnecting needed
@ -472,11 +472,11 @@ namespace CryptoExchange.Net.Sockets
subscriptionList = subscriptions.Where(h => h.Request != null).ToList();
// Foreach subscription which is subscribed by a subscription request we will need to resend that request to resubscribe
for (var i = 0; i < subscriptionList.Count; i += socketClient.MaxConcurrentResubscriptionsPerSocket)
for (var i = 0; i < subscriptionList.Count; i += socketClient.ClientOptions.MaxConcurrentResubscriptionsPerSocket)
{
var success = true;
var taskList = new List<Task>();
foreach (var subscription in subscriptionList.Skip(i).Take(socketClient.MaxConcurrentResubscriptionsPerSocket))
foreach (var subscription in subscriptionList.Skip(i).Take(socketClient.ClientOptions.MaxConcurrentResubscriptionsPerSocket))
{
if (!Socket.IsOpen)
continue;

View File

@ -61,7 +61,6 @@ namespace CryptoExchange.Net.Sockets
/// <param name="request"></param>
/// <param name="userSubscription"></param>
/// <param name="dataHandler"></param>
/// <param name="ct"></param>
/// <returns></returns>
public static SocketSubscription CreateForRequest(int id, object request, bool userSubscription,
Action<MessageEvent> dataHandler)
@ -76,7 +75,6 @@ namespace CryptoExchange.Net.Sockets
/// <param name="identifier"></param>
/// <param name="userSubscription"></param>
/// <param name="dataHandler"></param>
/// <param name="ct"></param>
/// <returns></returns>
public static SocketSubscription CreateForIdentifier(int id, string identifier, bool userSubscription,
Action<MessageEvent> dataHandler)