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 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
Assert.IsTrue(client.BaseAddress == "http://test.address.com/"); Assert.IsTrue(client.BaseAddress == "http://test.address.com/");
Assert.IsTrue(client.ReconnectInterval.TotalSeconds == 6); Assert.IsTrue(client.ClientOptions.ReconnectInterval.TotalSeconds == 6);
} }
[TestCase(true)] [TestCase(true)]

View File

@ -21,35 +21,19 @@ namespace CryptoExchange.Net
/// </summary> /// </summary>
public abstract class BaseClient : IDisposable public abstract class BaseClient : IDisposable
{ {
/// <summary>
/// The address of the client
/// </summary>
public string BaseAddress { get; }
/// <summary> /// <summary>
/// The name of the exchange the client is for /// The name of the exchange the client is for
/// </summary> /// </summary>
public string ExchangeName { get; } internal string ExchangeName { get; }
/// <summary> /// <summary>
/// The log object /// The log object
/// </summary> /// </summary>
protected internal Log log; protected internal Log log;
/// <summary> /// <summary>
/// The api proxy
/// </summary>
protected ApiProxy? apiProxy;
/// <summary>
/// The authentication provider /// The authentication provider
/// </summary> /// </summary>
protected internal AuthenticationProvider? authProvider; protected internal AuthenticationProvider? authProvider;
/// <summary> /// <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 /// The last used id, use NextId() to get the next id and up this
/// </summary> /// </summary>
protected static int lastId; protected static int lastId;
@ -68,9 +52,9 @@ namespace CryptoExchange.Net
}); });
/// <summary> /// <summary>
/// Last id used /// Provided client options
/// </summary> /// </summary>
public static int LastId => lastId; public ClientOptions ClientOptions { get; }
/// <summary> /// <summary>
/// ctor /// ctor
@ -85,13 +69,12 @@ namespace CryptoExchange.Net
log.UpdateWriters(options.LogWriters); log.UpdateWriters(options.LogWriters);
log.Level = options.LogLevel; log.Level = options.LogLevel;
ClientOptions = options;
ExchangeName = exchangeName; ExchangeName = exchangeName;
OutputOriginalData = options.OutputOriginalData; //BaseAddress = options.BaseAddress;
BaseAddress = options.BaseAddress;
apiProxy = options.Proxy;
log.Write(LogLevel.Debug, $"Client configuration: {options}, CryptoExchange.Net: v{typeof(BaseClient).Assembly.GetName().Version}, {ExchangeName}.Net: v{GetType().Assembly.GetName().Version}"); 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> /// <summary>
@ -145,11 +128,10 @@ namespace CryptoExchange.Net
/// </summary> /// </summary>
/// <typeparam name="T">The type to deserialize into</typeparam> /// <typeparam name="T">The type to deserialize into</typeparam>
/// <param name="data">The data to deserialize</param> /// <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="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> /// <param name="requestId">Id of the request the data is returned from (used for grouping logging by request)</param>
/// <returns></returns> /// <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); var tokenResult = ValidateJson(data);
if (!tokenResult) if (!tokenResult)
@ -158,7 +140,7 @@ namespace CryptoExchange.Net
return new CallResult<T>(default, tokenResult.Error); return new CallResult<T>(default, tokenResult.Error);
} }
return Deserialize<T>(tokenResult.Data, checkObject, serializer, requestId); return Deserialize<T>(tokenResult.Data, serializer, requestId);
} }
/// <summary> /// <summary>
@ -166,39 +148,16 @@ namespace CryptoExchange.Net
/// </summary> /// </summary>
/// <typeparam name="T">The type to deserialize into</typeparam> /// <typeparam name="T">The type to deserialize into</typeparam>
/// <param name="obj">The data to deserialize</param> /// <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="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> /// <param name="requestId">Id of the request the data is returned from (used for grouping logging by request)</param>
/// <returns></returns> /// <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) if (serializer == null)
serializer = defaultSerializer; serializer = defaultSerializer;
try 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); return new CallResult<T>(obj.ToObject<T>(serializer), null);
} }
catch (JsonReaderException jre) 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 // 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 // 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); var data = await reader.ReadToEndAsync().ConfigureAwait(false);
log.Write(LogLevel.Debug, $"{(requestId != null ? $"[{requestId}] ": "")}Response received{(elapsedMilliseconds != null ? $" in {elapsedMilliseconds}" : " ")}ms: {data}"); log.Write(LogLevel.Debug, $"{(requestId != null ? $"[{requestId}] ": "")}Response received{(elapsedMilliseconds != null ? $" in {elapsedMilliseconds}" : " ")}ms: {data}");
var result = Deserialize<T>(data, null, serializer, requestId); var result = Deserialize<T>(data, serializer, requestId);
if(OutputOriginalData) if(ClientOptions.OutputOriginalData)
result.OriginalData = data; result.OriginalData = data;
return result; return result;
} }
@ -308,116 +267,6 @@ namespace CryptoExchange.Net
return await reader.ReadToEndAsync().ConfigureAwait(false); 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> /// <summary>
/// Generate a new unique id. The id is staticly stored so it is guarenteed to be unique across different client instances /// Generate a new unique id. The id is staticly stored so it is guarenteed to be unique across different client instances
/// </summary> /// </summary>

View File

@ -204,11 +204,6 @@
The base for all clients, websocket client and rest client The base for all clients, websocket client and rest client
</summary> </summary>
</member> </member>
<member name="P:CryptoExchange.Net.BaseClient.BaseAddress">
<summary>
The address of the client
</summary>
</member>
<member name="P:CryptoExchange.Net.BaseClient.ExchangeName"> <member name="P:CryptoExchange.Net.BaseClient.ExchangeName">
<summary> <summary>
The name of the exchange the client is for The name of the exchange the client is for
@ -219,26 +214,11 @@
The log object The log object
</summary> </summary>
</member> </member>
<member name="F:CryptoExchange.Net.BaseClient.apiProxy">
<summary>
The api proxy
</summary>
</member>
<member name="F:CryptoExchange.Net.BaseClient.authProvider"> <member name="F:CryptoExchange.Net.BaseClient.authProvider">
<summary> <summary>
The authentication provider The authentication provider
</summary> </summary>
</member> </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"> <member name="F:CryptoExchange.Net.BaseClient.lastId">
<summary> <summary>
The last used id, use NextId() to get the next id and up this The last used id, use NextId() to get the next id and up this
@ -254,9 +234,9 @@
A default serializer A default serializer
</summary> </summary>
</member> </member>
<member name="P:CryptoExchange.Net.BaseClient.LastId"> <member name="P:CryptoExchange.Net.BaseClient.ClientOptions">
<summary> <summary>
Last id used Provided client options
</summary> </summary>
</member> </member>
<member name="M:CryptoExchange.Net.BaseClient.#ctor(System.String,CryptoExchange.Net.Objects.ClientOptions,CryptoExchange.Net.Authentication.AuthenticationProvider)"> <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> <param name="data">The data to parse</param>
<returns></returns> <returns></returns>
</member> </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> <summary>
Deserialize a string into an object Deserialize a string into an object
</summary> </summary>
<typeparam name="T">The type to deserialize into</typeparam> <typeparam name="T">The type to deserialize into</typeparam>
<param name="data">The data to deserialize</param> <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="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> <param name="requestId">Id of the request the data is returned from (used for grouping logging by request)</param>
<returns></returns> <returns></returns>
</member> </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> <summary>
Deserialize a JToken into an object Deserialize a JToken into an object
</summary> </summary>
<typeparam name="T">The type to deserialize into</typeparam> <typeparam name="T">The type to deserialize into</typeparam>
<param name="obj">The data to deserialize</param> <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="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> <param name="requestId">Id of the request the data is returned from (used for grouping logging by request)</param>
<returns></returns> <returns></returns>
@ -1233,31 +1211,11 @@
The factory for creating requests. Used for unit testing The factory for creating requests. Used for unit testing
</summary> </summary>
</member> </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"> <member name="P:CryptoExchange.Net.Interfaces.IRestClient.TotalRequestsMade">
<summary> <summary>
The total amount of requests made The total amount of requests made
</summary> </summary>
</member> </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)"> <member name="M:CryptoExchange.Net.Interfaces.IRestClient.AddRateLimiter(CryptoExchange.Net.Interfaces.IRateLimiter)">
<summary> <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"/>. 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 Removes all rate limiters from this client
</summary> </summary>
</member> </member>
<member name="M:CryptoExchange.Net.Interfaces.IRestClient.Ping(System.Threading.CancellationToken)"> <member name="P:CryptoExchange.Net.Interfaces.IRestClient.ClientOptions">
<summary> <summary>
Ping to see if the server is reachable Client options
</summary> </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>
<member name="T:CryptoExchange.Net.Interfaces.ISocketClient"> <member name="T:CryptoExchange.Net.Interfaces.ISocketClient">
<summary> <summary>
Base class for socket API implementations Base class for socket API implementations
</summary> </summary>
</member> </member>
<member name="P:CryptoExchange.Net.Interfaces.ISocketClient.SocketFactory"> <member name="P:CryptoExchange.Net.Interfaces.ISocketClient.ClientOptions">
<summary> <summary>
The factory for creating sockets. Used for unit testing Client options
</summary> </summary>
</member> </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"> <member name="P:CryptoExchange.Net.Interfaces.ISocketClient.IncomingKbps">
<summary> <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> </summary>
</member> </member>
<member name="M:CryptoExchange.Net.Interfaces.ISocketClient.UnsubscribeAsync(CryptoExchange.Net.Sockets.UpdateSubscription)"> <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 If true, the CallResult and DataEvent objects will also include the originally received json data in the OriginalData property
</summary> </summary>
</member> </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"> <member name="M:CryptoExchange.Net.Objects.BaseOptions.ToString">
<inheritdoc /> <inheritdoc />
</member> </member>
@ -2281,39 +2202,11 @@
Base for order book options Base for order book options
</summary> </summary>
</member> </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"> <member name="P:CryptoExchange.Net.Objects.OrderBookOptions.ChecksumValidationEnabled">
<summary> <summary>
Whether or not checksum validation is enabled. Default is true, disabling will ignore checksum messages. Whether or not checksum validation is enabled. Default is true, disabling will ignore checksum messages.
</summary> </summary>
</member> </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"> <member name="T:CryptoExchange.Net.Objects.ClientOptions">
<summary> <summary>
Base client options Base client options
@ -2329,21 +2222,18 @@
The api credentials The api credentials
</summary> </summary>
</member> </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"> <member name="P:CryptoExchange.Net.Objects.ClientOptions.Proxy">
<summary> <summary>
Proxy to use Proxy to use
</summary> </summary>
</member> </member>
<member name="M:CryptoExchange.Net.Objects.ClientOptions.#ctor(System.String)"> <member name="M:CryptoExchange.Net.Objects.ClientOptions.Copy``1(``0,``0)">
<summary> <summary>
ctor Copy the values of the def to the input
</summary> </summary>
<param name="baseAddress">The base address to use</param> <typeparam name="T"></typeparam>
<param name="input"></param>
<param name="def"></param>
</member> </member>
<member name="M:CryptoExchange.Net.Objects.ClientOptions.ToString"> <member name="M:CryptoExchange.Net.Objects.ClientOptions.ToString">
<inheritdoc /> <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 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> </summary>
</member> </member>
<member name="M:CryptoExchange.Net.Objects.RestClientOptions.#ctor(System.String)"> <member name="M:CryptoExchange.Net.Objects.RestClientOptions.Copy``1(``0,``0)">
<summary> <summary>
ctor Copy the values of the def to the input
</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
</summary> </summary>
<typeparam name="T"></typeparam> <typeparam name="T"></typeparam>
<returns></returns> <param name="input"></param>
<param name="def"></param>
</member> </member>
<member name="M:CryptoExchange.Net.Objects.RestClientOptions.ToString"> <member name="M:CryptoExchange.Net.Objects.RestClientOptions.ToString">
<inheritdoc /> <inheritdoc />
@ -2443,18 +2321,13 @@
single connection will also increase the amount of traffic on that single connection, potentially leading to issues. single connection will also increase the amount of traffic on that single connection, potentially leading to issues.
</summary> </summary>
</member> </member>
<member name="M:CryptoExchange.Net.Objects.SocketClientOptions.#ctor(System.String)"> <member name="M:CryptoExchange.Net.Objects.SocketClientOptions.Copy``1(``0,``0)">
<summary> <summary>
ctor Copy the values of the def to the input
</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
</summary> </summary>
<typeparam name="T"></typeparam> <typeparam name="T"></typeparam>
<returns></returns> <param name="input"></param>
<param name="def"></param>
</member> </member>
<member name="M:CryptoExchange.Net.Objects.SocketClientOptions.ToString"> <member name="M:CryptoExchange.Net.Objects.SocketClientOptions.ToString">
<inheritdoc /> <inheritdoc />
@ -2514,6 +2387,16 @@
The log The log
</summary> </summary>
</member> </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"> <member name="F:CryptoExchange.Net.OrderBook.SymbolOrderBook.bookSet">
<summary> <summary>
If order book is set If order book is set
@ -2599,10 +2482,11 @@
BestBid/BesAsk returned as a pair BestBid/BesAsk returned as a pair
</summary> </summary>
</member> </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> <summary>
ctor ctor
</summary> </summary>
<param name="id"></param>
<param name="symbol"></param> <param name="symbol"></param>
<param name="options"></param> <param name="options"></param>
</member> </member>
@ -2936,16 +2820,6 @@
What request body should be set when no data is send (only used in combination with postParametersPosition.InBody) What request body should be set when no data is send (only used in combination with postParametersPosition.InBody)
</summary> </summary>
</member> </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"> <member name="P:CryptoExchange.Net.RestClient.RateLimiters">
<summary> <summary>
List of rate limiters List of rate limiters
@ -2961,6 +2835,11 @@
Request headers to be sent with each request Request headers to be sent with each request
</summary> </summary>
</member> </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)"> <member name="M:CryptoExchange.Net.RestClient.#ctor(System.String,CryptoExchange.Net.Objects.RestClientOptions,CryptoExchange.Net.Authentication.AuthenticationProvider)">
<summary> <summary>
ctor ctor
@ -2980,18 +2859,6 @@
Removes all rate limiters from this client Removes all rate limiters from this client
</summary> </summary>
</member> </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})"> <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> <summary>
Execute a request to the uri and deserialize the response into the provided type parameter Execute a request to the uri and deserialize the response into the provided type parameter
@ -3077,35 +2944,11 @@
Semaphore used while creating sockets Semaphore used while creating sockets
</summary> </summary>
</member> </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"> <member name="P:CryptoExchange.Net.SocketClient.MaxSocketConnections">
<summary> <summary>
The max amount of concurrent socket connections The max amount of concurrent socket connections
</summary> </summary>
</member> </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"> <member name="F:CryptoExchange.Net.SocketClient.dataInterpreterBytes">
<summary> <summary>
Delegate used for processing byte data received from socket connections before it is processed by handlers 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 The current kilobytes per second of data being received by all connection from this client, averaged over the last 3 seconds
</summary> </summary>
</member> </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)"> <member name="M:CryptoExchange.Net.SocketClient.#ctor(System.String,CryptoExchange.Net.Objects.SocketClientOptions,CryptoExchange.Net.Authentication.AuthenticationProvider)">
<summary> <summary>
ctor ctor
@ -3312,7 +3160,7 @@
<param name="message"></param> <param name="message"></param>
<returns></returns> <returns></returns>
</member> </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> <summary>
Add a subscription to a connection Add a subscription to a connection
</summary> </summary>
@ -3943,7 +3791,6 @@
<param name="request"></param> <param name="request"></param>
<param name="userSubscription"></param> <param name="userSubscription"></param>
<param name="dataHandler"></param> <param name="dataHandler"></param>
<param name="ct"></param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.Sockets.SocketSubscription.CreateForIdentifier(System.Int32,System.String,System.Boolean,System.Action{CryptoExchange.Net.Sockets.MessageEvent})"> <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="identifier"></param>
<param name="userSubscription"></param> <param name="userSubscription"></param>
<param name="dataHandler"></param> <param name="dataHandler"></param>
<param name="ct"></param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.Sockets.SocketSubscription.InvokeExceptionHandler(System.Exception)"> <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})"> <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 /> <inheritdoc />
</member> </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> </members>
</doc> </doc>

View File

@ -17,31 +17,11 @@ namespace CryptoExchange.Net.Interfaces
/// </summary> /// </summary>
IRequestFactory RequestFactory { get; set; } 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> /// <summary>
/// The total amount of requests made /// The total amount of requests made
/// </summary> /// </summary>
int TotalRequestsMade { get; } int TotalRequestsMade { get; }
/// <summary>
/// The base address of the API
/// </summary>
string BaseAddress { get; }
/// <summary>
/// Client name
/// </summary>
string ExchangeName { get; }
/// <summary> /// <summary>
/// Adds a rate limiter to the client. There are 2 choices, the <see cref="RateLimiterTotal"/> and the <see cref="RateLimiterPerEndpoint"/>. /// Adds a rate limiter to the client. There are 2 choices, the <see cref="RateLimiterTotal"/> and the <see cref="RateLimiterPerEndpoint"/>.
/// </summary> /// </summary>
@ -54,15 +34,8 @@ namespace CryptoExchange.Net.Interfaces
void RemoveRateLimiters(); void RemoveRateLimiters();
/// <summary> /// <summary>
/// Ping to see if the server is reachable /// Client options
/// </summary> /// </summary>
/// <returns>The roundtrip time of the ping request</returns> RestClientOptions ClientOptions { get; }
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);
} }
} }

View File

@ -11,48 +11,14 @@ namespace CryptoExchange.Net.Interfaces
public interface ISocketClient: IDisposable public interface ISocketClient: IDisposable
{ {
/// <summary> /// <summary>
/// The factory for creating sockets. Used for unit testing /// Client options
/// </summary> /// </summary>
IWebsocketFactory SocketFactory { get; set; } SocketClientOptions ClientOptions { get; }
/// <summary> /// <summary>
/// The time in between reconnect attempts /// Incoming kilobytes per second of data
/// </summary> /// </summary>
TimeSpan ReconnectInterval { get; } public double IncomingKbps { 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; }
/// <summary> /// <summary>
/// Unsubscribe from a stream /// Unsubscribe from a stream

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
@ -16,7 +17,7 @@ namespace CryptoExchange.Net.Objects
/// <summary> /// <summary>
/// The minimum log level to output. Setting it to null will send all messages to the registered ILoggers. /// The minimum log level to output. Setting it to null will send all messages to the registered ILoggers.
/// </summary> /// </summary>
public LogLevel? LogLevel { get; set; } = Microsoft.Extensions.Logging.LogLevel.Information; public LogLevel LogLevel { get; set; } = LogLevel.Information;
/// <summary> /// <summary>
/// The log writers /// The log writers
@ -28,6 +29,19 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public bool OutputOriginalData { get; set; } = false; 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 /> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {
@ -39,47 +53,11 @@ namespace CryptoExchange.Net.Objects
/// Base for order book options /// Base for order book options
/// </summary> /// </summary>
public class OrderBookOptions : BaseOptions public class OrderBookOptions : BaseOptions
{ {
/// <summary>
/// The name of the order book implementation
/// </summary>
public string OrderBookName { get; }
/// <summary> /// <summary>
/// Whether or not checksum validation is enabled. Default is true, disabling will ignore checksum messages. /// Whether or not checksum validation is enabled. Default is true, disabling will ignore checksum messages.
/// </summary> /// </summary>
public bool ChecksumValidationEnabled { get; set; } = true; 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> /// <summary>
@ -87,7 +65,7 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public class ClientOptions : BaseOptions public class ClientOptions : BaseOptions
{ {
private string _baseAddress; private string _baseAddress = string.Empty;
/// <summary> /// <summary>
/// The base address of the client /// The base address of the client
@ -97,6 +75,9 @@ namespace CryptoExchange.Net.Objects
get => _baseAddress; get => _baseAddress;
set set
{ {
if (value == null)
return;
var newValue = value; var newValue = value;
if (!newValue.EndsWith("/")) if (!newValue.EndsWith("/"))
newValue += "/"; newValue += "/";
@ -109,25 +90,24 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public ApiCredentials? ApiCredentials { get; set; } 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> /// <summary>
/// Proxy to use /// Proxy to use
/// </summary> /// </summary>
public ApiProxy? Proxy { get; set; } public ApiProxy? Proxy { get; set; }
/// <summary> /// <summary>
/// ctor /// Copy the values of the def to the input
/// </summary> /// </summary>
/// <param name="baseAddress">The base address to use</param> /// <typeparam name="T"></typeparam>
#pragma warning disable 8618 /// <param name="input"></param>
public ClientOptions(string baseAddress) /// <param name="def"></param>
#pragma warning restore 8618 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 /> /// <inheritdoc />
@ -163,44 +143,19 @@ namespace CryptoExchange.Net.Objects
public HttpClient? HttpClient { get; set; } public HttpClient? HttpClient { get; set; }
/// <summary> /// <summary>
/// ctor /// Copy the values of the def to the input
/// </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
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns></returns> /// <param name="input"></param>
public T Copy<T>() where T : RestClientOptions, new() /// <param name="def"></param>
public new void Copy<T>(T input, T def) where T : RestClientOptions
{ {
var copy = new T base.Copy(input, def);
{
BaseAddress = BaseAddress, input.HttpClient = def.HttpClient;
LogLevel = LogLevel, input.RateLimiters = def.RateLimiters.ToList();
Proxy = Proxy, input.RateLimitingBehaviour = def.RateLimitingBehaviour;
LogWriters = LogWriters, input.RequestTimeout = def.RequestTimeout;
RateLimiters = RateLimiters,
RateLimitingBehaviour = RateLimitingBehaviour,
RequestTimeout = RequestTimeout,
HttpClient = HttpClient
};
if (ApiCredentials != null)
copy.ApiCredentials = ApiCredentials.Copy();
return copy;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -257,36 +212,23 @@ namespace CryptoExchange.Net.Objects
public int? SocketSubscriptionsCombineTarget { get; set; } public int? SocketSubscriptionsCombineTarget { get; set; }
/// <summary> /// <summary>
/// ctor /// Copy the values of the def to the input
/// </summary>
/// <param name="baseAddress">The base address to use</param>
public SocketClientOptions(string baseAddress) : base(baseAddress)
{
}
/// <summary>
/// Create a copy of the options
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns></returns> /// <param name="input"></param>
public T Copy<T>() where T : SocketClientOptions, new() /// <param name="def"></param>
public new void Copy<T>(T input, T def) where T : SocketClientOptions
{ {
var copy = new T base.Copy(input, def);
{
BaseAddress = BaseAddress,
LogLevel = LogLevel,
Proxy = Proxy,
LogWriters = LogWriters,
AutoReconnect = AutoReconnect,
ReconnectInterval = ReconnectInterval,
SocketResponseTimeout = SocketResponseTimeout,
SocketSubscriptionsCombineTarget = SocketSubscriptionsCombineTarget
};
if (ApiCredentials != null) input.AutoReconnect = def.AutoReconnect;
copy.ApiCredentials = ApiCredentials.Copy(); input.ReconnectInterval = def.ReconnectInterval;
input.MaxReconnectTries = def.MaxReconnectTries;
return copy; input.MaxResubscribeTries = def.MaxResubscribeTries;
input.MaxConcurrentResubscriptionsPerSocket = def.MaxConcurrentResubscriptionsPerSocket;
input.SocketResponseTimeout = def.SocketResponseTimeout;
input.SocketNoDataTimeout = def.SocketNoDataTimeout;
input.SocketSubscriptionsCombineTarget = def.SocketSubscriptionsCombineTarget;
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -35,8 +35,7 @@ namespace CryptoExchange.Net.OrderBook
private OrderBookStatus status; private OrderBookStatus status;
private UpdateSubscription? subscription; private UpdateSubscription? subscription;
private readonly bool sequencesAreConsecutive;
private readonly bool strictLevels;
private readonly bool validateChecksum; private readonly bool validateChecksum;
private bool _stopProcessing; private bool _stopProcessing;
@ -53,6 +52,16 @@ namespace CryptoExchange.Net.OrderBook
/// </summary> /// </summary>
protected Log log; 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> /// <summary>
/// If order book is set /// If order book is set
/// </summary> /// </summary>
@ -199,9 +208,10 @@ namespace CryptoExchange.Net.OrderBook
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
/// <param name="id"></param>
/// <param name="symbol"></param> /// <param name="symbol"></param>
/// <param name="options"></param> /// <param name="options"></param>
protected SymbolOrderBook(string symbol, OrderBookOptions options) protected SymbolOrderBook(string id, string symbol, OrderBookOptions options)
{ {
if (symbol == null) if (symbol == null)
throw new ArgumentNullException(nameof(symbol)); throw new ArgumentNullException(nameof(symbol));
@ -209,13 +219,11 @@ namespace CryptoExchange.Net.OrderBook
if (options == null) if (options == null)
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(options));
Id = options.OrderBookName; Id = id;
processBuffer = new List<ProcessBufferRangeSequenceEntry>(); processBuffer = new List<ProcessBufferRangeSequenceEntry>();
_processQueue = new ConcurrentQueue<object>(); _processQueue = new ConcurrentQueue<object>();
_queueEvent = new AutoResetEvent(false); _queueEvent = new AutoResetEvent(false);
sequencesAreConsecutive = options.SequenceNumbersAreConsecutive;
strictLevels = options.StrictLevels;
validateChecksum = options.ChecksumValidationEnabled; validateChecksum = options.ChecksumValidationEnabled;
Symbol = symbol; Symbol = symbol;
Status = OrderBookStatus.Disconnected; Status = OrderBookStatus.Disconnected;
@ -223,7 +231,7 @@ namespace CryptoExchange.Net.OrderBook
asks = new SortedList<decimal, ISymbolOrderBookEntry>(); asks = new SortedList<decimal, ISymbolOrderBookEntry>();
bids = new SortedList<decimal, ISymbolOrderBookEntry>(new DescComparer<decimal>()); 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() }; var writers = options.LogWriters ?? new List<ILogger> { new DebugLogger() };
log.UpdateWriters(writers.ToList()); 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) /// What request body should be set when no data is send (only used in combination with postParametersPosition.InBody)
/// </summary> /// </summary>
protected string requestBodyEmptyContent = "{}"; 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> /// <summary>
/// List of rate limiters /// List of rate limiters
/// </summary> /// </summary>
public IEnumerable<IRateLimiter> RateLimiters { get; private set; } protected IEnumerable<IRateLimiter> RateLimiters { get; private set; }
/// <summary> /// <summary>
/// Total requests made by this client /// Total requests made by this client
/// </summary> /// </summary>
@ -84,6 +77,11 @@ namespace CryptoExchange.Net
/// </summary> /// </summary>
protected Dictionary<string, string>? StandardRequestHeaders { get; set; } protected Dictionary<string, string>? StandardRequestHeaders { get; set; }
/// <summary>
/// Client options
/// </summary>
public new RestClientOptions ClientOptions { get; }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
@ -95,9 +93,9 @@ namespace CryptoExchange.Net
if (exchangeOptions == null) if (exchangeOptions == null)
throw new ArgumentNullException(nameof(exchangeOptions)); throw new ArgumentNullException(nameof(exchangeOptions));
RequestTimeout = exchangeOptions.RequestTimeout; ClientOptions = exchangeOptions;
RequestFactory.Configure(exchangeOptions.RequestTimeout, exchangeOptions.Proxy, exchangeOptions.HttpClient); RequestFactory.Configure(exchangeOptions.RequestTimeout, exchangeOptions.Proxy, exchangeOptions.HttpClient);
RateLimitBehaviour = exchangeOptions.RateLimitingBehaviour;
var rateLimiters = new List<IRateLimiter>(); var rateLimiters = new List<IRateLimiter>();
foreach (var rateLimiter in exchangeOptions.RateLimiters) foreach (var rateLimiter in exchangeOptions.RateLimiters)
rateLimiters.Add(rateLimiter); rateLimiters.Add(rateLimiter);
@ -126,48 +124,6 @@ namespace CryptoExchange.Net
RateLimiters = new List<IRateLimiter>(); 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> /// <summary>
/// Execute a request to the uri and deserialize the response into the provided type parameter /// Execute a request to the uri and deserialize the response into the provided type parameter
/// </summary> /// </summary>
@ -210,7 +166,7 @@ namespace CryptoExchange.Net
var request = ConstructRequest(uri, method, parameters, signed, paramsPosition, arraySerialization ?? this.arraySerialization, requestId, additionalHeaders); var request = ConstructRequest(uri, method, parameters, signed, paramsPosition, arraySerialization ?? this.arraySerialization, requestId, additionalHeaders);
foreach (var limiter in RateLimiters) 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) if (!limitResult.Success)
{ {
log.Write(LogLevel.Information, $"[{requestId}] Request {uri.AbsolutePath} failed because of rate limit"); 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)}]")); 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); 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); return WebCallResult<T>.CreateErrorResult(response.StatusCode, response.ResponseHeaders, error);
// Not an error, so continue deserializing // Not an error, so continue deserializing
var deserializeResult = Deserialize<T>(parseResult.Data, null, deserializer, request.RequestId); var deserializeResult = Deserialize<T>(parseResult.Data, deserializer, request.RequestId);
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, OutputOriginalData ? data: null, deserializeResult.Data, deserializeResult.Error); return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, ClientOptions.OutputOriginalData ? data: null, deserializeResult.Data, deserializeResult.Error);
} }
else else
{ {
@ -287,7 +243,7 @@ namespace CryptoExchange.Net
responseStream.Close(); responseStream.Close();
response.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 else

View File

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

View File

@ -129,7 +129,7 @@ namespace CryptoExchange.Net.Sockets
subscriptions = new List<SocketSubscription>(); subscriptions = new List<SocketSubscription>();
Socket = socket; Socket = socket;
Socket.Timeout = client.SocketNoDataTimeout; Socket.Timeout = client.ClientOptions.SocketNoDataTimeout;
Socket.OnMessage += ProcessMessage; Socket.OnMessage += ProcessMessage;
Socket.OnClose += SocketOnClose; Socket.OnClose += SocketOnClose;
Socket.OnOpen += SocketOnOpen; Socket.OnOpen += SocketOnOpen;
@ -183,7 +183,7 @@ namespace CryptoExchange.Net.Sockets
} }
// Message was not a request response, check data handlers // 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 (!HandleData(messageEvent) && !handledResponse)
{ {
if (!socketClient.UnhandledMessageExpected) if (!socketClient.UnhandledMessageExpected)
@ -330,7 +330,7 @@ namespace CryptoExchange.Net.Sockets
} }
} }
if (socketClient.AutoReconnect && ShouldReconnect) if (socketClient.ClientOptions.AutoReconnect && ShouldReconnect)
{ {
if (Socket.Reconnecting) if (Socket.Reconnecting)
return; // Already reconnecting return; // Already reconnecting
@ -338,7 +338,7 @@ namespace CryptoExchange.Net.Sockets
Socket.Reconnecting = true; Socket.Reconnecting = true;
DisconnectTime = DateTime.UtcNow; 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) if (!lostTriggered)
{ {
lostTriggered = true; lostTriggered = true;
@ -350,7 +350,7 @@ namespace CryptoExchange.Net.Sockets
while (ShouldReconnect) while (ShouldReconnect)
{ {
// Wait a bit before attempting reconnect // Wait a bit before attempting reconnect
await Task.Delay(socketClient.ReconnectInterval).ConfigureAwait(false); await Task.Delay(socketClient.ClientOptions.ReconnectInterval).ConfigureAwait(false);
if (!ShouldReconnect) if (!ShouldReconnect)
{ {
// Should reconnect changed to false while waiting to reconnect // Should reconnect changed to false while waiting to reconnect
@ -363,8 +363,8 @@ namespace CryptoExchange.Net.Sockets
{ {
ReconnectTry++; ReconnectTry++;
ResubscribeTry = 0; ResubscribeTry = 0;
if (socketClient.MaxReconnectTries != null if (socketClient.ClientOptions.MaxReconnectTries != null
&& ReconnectTry >= socketClient.MaxReconnectTries) && ReconnectTry >= socketClient.ClientOptions.MaxReconnectTries)
{ {
log.Write(LogLevel.Debug, $"Socket {Socket.Id} failed to reconnect after {ReconnectTry} tries, closing"); log.Write(LogLevel.Debug, $"Socket {Socket.Id} failed to reconnect after {ReconnectTry} tries, closing");
ShouldReconnect = false; ShouldReconnect = false;
@ -377,7 +377,7 @@ namespace CryptoExchange.Net.Sockets
break; 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; continue;
} }
@ -392,8 +392,8 @@ namespace CryptoExchange.Net.Sockets
{ {
ResubscribeTry++; ResubscribeTry++;
if (socketClient.MaxResubscribeTries != null && if (socketClient.ClientOptions.MaxResubscribeTries != null &&
ResubscribeTry >= socketClient.MaxResubscribeTries) ResubscribeTry >= socketClient.ClientOptions.MaxResubscribeTries)
{ {
log.Write(LogLevel.Debug, $"Socket {Socket.Id} failed to resubscribe after {ResubscribeTry} tries, closing"); log.Write(LogLevel.Debug, $"Socket {Socket.Id} failed to resubscribe after {ResubscribeTry} tries, closing");
ShouldReconnect = false; ShouldReconnect = false;
@ -405,7 +405,7 @@ namespace CryptoExchange.Net.Sockets
_ = Task.Run(() => ConnectionClosed?.Invoke()); _ = Task.Run(() => ConnectionClosed?.Invoke());
} }
else 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) if (Socket.IsOpen)
await Socket.CloseAsync().ConfigureAwait(false); await Socket.CloseAsync().ConfigureAwait(false);
@ -431,7 +431,7 @@ namespace CryptoExchange.Net.Sockets
} }
else else
{ {
if (!socketClient.AutoReconnect && ShouldReconnect) if (!socketClient.ClientOptions.AutoReconnect && ShouldReconnect)
_ = Task.Run(() => ConnectionClosed?.Invoke()); _ = Task.Run(() => ConnectionClosed?.Invoke());
// No reconnecting needed // No reconnecting needed
@ -472,11 +472,11 @@ namespace CryptoExchange.Net.Sockets
subscriptionList = subscriptions.Where(h => h.Request != null).ToList(); 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 // 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 success = true;
var taskList = new List<Task>(); 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) if (!Socket.IsOpen)
continue; continue;

View File

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