diff --git a/CryptoExchange.Net/Authentication/AuthenticationProvider.cs b/CryptoExchange.Net/Authentication/AuthenticationProvider.cs
index 7e143d4..55e9d80 100644
--- a/CryptoExchange.Net/Authentication/AuthenticationProvider.cs
+++ b/CryptoExchange.Net/Authentication/AuthenticationProvider.cs
@@ -49,11 +49,11 @@ namespace CryptoExchange.Net.Authentication
/// The method of the request
/// If the requests should be authenticated
/// Array serialization type
- /// The position where the providedParameters should go
/// The formatting of the request body
/// Parameters that need to be in the Uri of the request. Should include the provided parameters if they should go in the uri
/// Parameters that need to be in the body of the request. Should include the provided parameters if they should go in the body
/// The headers that should be send with the request
+ /// The position where the providedParameters should go
public abstract void AuthenticateRequest(
RestApiClient apiClient,
Uri uri,
diff --git a/CryptoExchange.Net/Clients/RestApiClient.cs b/CryptoExchange.Net/Clients/RestApiClient.cs
index 68c2af0..857879d 100644
--- a/CryptoExchange.Net/Clients/RestApiClient.cs
+++ b/CryptoExchange.Net/Clients/RestApiClient.cs
@@ -150,23 +150,60 @@ namespace CryptoExchange.Net.Clients
/// Additional headers for this request
/// Override the request weight for this request definition, for example when the weight depends on the parameters
///
- protected virtual async Task> SendAsync(
+ protected virtual Task> SendAsync(
string baseAddress,
RequestDefinition definition,
ParameterCollection? parameters,
CancellationToken cancellationToken,
Dictionary? additionalHeaders = null,
int? weight = null) where T : class
+ {
+ var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
+ return SendAsync(
+ baseAddress,
+ definition,
+ parameterPosition == HttpMethodParameterPosition.InUri ? parameters : null,
+ parameterPosition == HttpMethodParameterPosition.InBody ? parameters : null,
+ cancellationToken,
+ additionalHeaders,
+ weight);
+ }
+
+ ///
+ /// Send a request to the base address based on the request definition
+ ///
+ /// Response type
+ /// Host and schema
+ /// Request definition
+ /// Request query parameters
+ /// Request body parameters
+ /// Cancellation token
+ /// Additional headers for this request
+ /// Override the request weight for this request definition, for example when the weight depends on the parameters
+ ///
+ protected virtual async Task> SendAsync(
+ string baseAddress,
+ RequestDefinition definition,
+ ParameterCollection? uriParameters,
+ ParameterCollection? bodyParameters,
+ CancellationToken cancellationToken,
+ Dictionary? additionalHeaders = null,
+ int? weight = null) where T : class
{
int currentTry = 0;
while (true)
{
currentTry++;
- var prepareResult = await PrepareAsync(baseAddress, definition, parameters, cancellationToken, additionalHeaders, weight).ConfigureAwait(false);
+ var prepareResult = await PrepareAsync(baseAddress, definition, cancellationToken, additionalHeaders, weight).ConfigureAwait(false);
if (!prepareResult)
return new WebCallResult(prepareResult.Error!);
- var request = CreateRequest(baseAddress, definition, parameters, additionalHeaders);
+ var request = CreateRequest(
+ baseAddress,
+ definition,
+ uriParameters,
+ bodyParameters,
+ additionalHeaders);
_logger.RestApiSendRequest(request.RequestId, definition, request.Content, request.Uri.Query, string.Join(", ", request.GetHeaders().Select(h => h.Key + $"=[{string.Join(",", h.Value)}]")));
TotalRequestsMade++;
var result = await GetResponseAsync(request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false);
@@ -187,7 +224,6 @@ namespace CryptoExchange.Net.Clients
///
/// Host and schema
/// Request definition
- /// Request parameters
/// Cancellation token
/// Additional headers for this request
/// Override the request weight for this request
@@ -196,7 +232,6 @@ namespace CryptoExchange.Net.Clients
protected virtual async Task PrepareAsync(
string baseAddress,
RequestDefinition definition,
- ParameterCollection? parameters,
CancellationToken cancellationToken,
Dictionary? additionalHeaders = null,
int? weight = null)
@@ -264,25 +299,27 @@ namespace CryptoExchange.Net.Clients
///
/// Host and schema
/// Request definition
- /// The parameters of the request
+ /// The query parameters of the request
+ /// The body parameters of the request
/// Additional headers to send with the request
///
protected virtual IRequest CreateRequest(
string baseAddress,
RequestDefinition definition,
- ParameterCollection? parameters,
+ ParameterCollection? uriParameters,
+ ParameterCollection? bodyParameters,
Dictionary? additionalHeaders)
{
- parameters ??= new ParameterCollection();
+ var uriParams = uriParameters == null ? new ParameterCollection() : CreateParameterDictionary(uriParameters);
+ var bodyParams = bodyParameters == null ? new ParameterCollection() : CreateParameterDictionary(bodyParameters);
+
var uri = new Uri(baseAddress.AppendPath(definition.Path));
- var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
var arraySerialization = definition.ArraySerialization ?? ArraySerialization;
var bodyFormat = definition.RequestBodyFormat ?? RequestBodyFormat;
var requestId = ExchangeHelpers.NextId();
+ var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
var headers = new Dictionary();
- var uriParameters = parameterPosition == HttpMethodParameterPosition.InUri ? CreateParameterDictionary(parameters) : new Dictionary();
- var bodyParameters = parameterPosition == HttpMethodParameterPosition.InBody ? CreateParameterDictionary(parameters) : new Dictionary();
if (AuthenticationProvider != null)
{
try
@@ -291,13 +328,14 @@ namespace CryptoExchange.Net.Clients
this,
uri,
definition.Method,
- uriParameters,
- bodyParameters,
+ uriParams,
+ bodyParams,
headers,
definition.Authenticated,
arraySerialization,
parameterPosition,
- bodyFormat);
+ bodyFormat
+ );
}
catch (Exception ex)
{
@@ -305,18 +343,8 @@ namespace CryptoExchange.Net.Clients
}
}
- // Sanity check
- foreach (var param in parameters)
- {
- if (!uriParameters.ContainsKey(param.Key) && !bodyParameters.ContainsKey(param.Key))
- {
- throw new Exception($"Missing parameter {param.Key} after authentication processing. AuthenticationProvider implementation " +
- $"should return provided parameters in either the uri or body parameters output");
- }
- }
-
// Add the auth parameters to the uri, start with a new URI to be able to sort the parameters including the auth parameters
- uri = uri.SetParameters(uriParameters, arraySerialization);
+ uri = uri.SetParameters(uriParams, arraySerialization);
var request = RequestFactory.Create(definition.Method, uri, requestId);
request.Accept = Constants.JsonContentHeader;
@@ -343,8 +371,8 @@ namespace CryptoExchange.Net.Clients
if (parameterPosition == HttpMethodParameterPosition.InBody)
{
var contentType = bodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
- if (bodyParameters.Count != 0)
- WriteParamBody(request, bodyParameters, contentType);
+ if (bodyParams.Count != 0)
+ WriteParamBody(request, bodyParams, contentType);
else
request.SetContent(RequestBodyEmptyContent, contentType);
}
@@ -739,7 +767,8 @@ namespace CryptoExchange.Net.Clients
signed,
arraySerialization,
parameterPosition,
- bodyFormat);
+ bodyFormat
+ );
}
catch (Exception ex)
{
diff --git a/CryptoExchange.Net/Clients/SocketApiClient.cs b/CryptoExchange.Net/Clients/SocketApiClient.cs
index 7f29850..406387d 100644
--- a/CryptoExchange.Net/Clients/SocketApiClient.cs
+++ b/CryptoExchange.Net/Clients/SocketApiClient.cs
@@ -278,10 +278,11 @@ namespace CryptoExchange.Net.Clients
///
/// Send a query on a socket connection to the BaseAddress and wait for the response
///
- /// Expected result type
+ /// Expected result type
+ /// The type returned to the caller
/// The query
///
- protected virtual Task> QueryAsync(Query query)
+ protected virtual Task> QueryAsync(Query query)
{
return QueryAsync(BaseAddress, query);
}
@@ -289,14 +290,15 @@ namespace CryptoExchange.Net.Clients
///
/// Send a query on a socket connection and wait for the response
///
- /// The expected result type
+ /// Expected result type
+ /// The type returned to the caller
/// The url for the request
/// The query
///
- protected virtual async Task> QueryAsync(string url, Query query)
+ protected virtual async Task> QueryAsync(string url, Query query)
{
if (_disposing)
- return new CallResult(new InvalidOperationError("Client disposed, can't query"));
+ return new CallResult(new InvalidOperationError("Client disposed, can't query"));
SocketConnection socketConnection;
var released = false;
@@ -305,7 +307,7 @@ namespace CryptoExchange.Net.Clients
{
var socketResult = await GetSocketConnection(url, query.Authenticated).ConfigureAwait(false);
if (!socketResult)
- return socketResult.As(default);
+ return socketResult.As(default);
socketConnection = socketResult.Data;
@@ -318,7 +320,7 @@ namespace CryptoExchange.Net.Clients
var connectResult = await ConnectIfNeededAsync(socketConnection, query.Authenticated).ConfigureAwait(false);
if (!connectResult)
- return new CallResult(connectResult.Error!);
+ return new CallResult(connectResult.Error!);
}
finally
{
@@ -329,10 +331,10 @@ namespace CryptoExchange.Net.Clients
if (socketConnection.PausedActivity)
{
_logger.HasBeenPausedCantSendQueryAtThisMoment(socketConnection.SocketId);
- return new CallResult(new ServerError("Socket is paused"));
+ return new CallResult(new ServerError("Socket is paused"));
}
- return await socketConnection.SendAndWaitQueryAsync(query).ConfigureAwait(false);
+ return await socketConnection.SendAndWaitQueryAsync(query).ConfigureAwait(false);
}
///
diff --git a/CryptoExchange.Net/Converters/SystemTextJson/DateTimeConverter.cs b/CryptoExchange.Net/Converters/SystemTextJson/DateTimeConverter.cs
index da52d66..5d480a1 100644
--- a/CryptoExchange.Net/Converters/SystemTextJson/DateTimeConverter.cs
+++ b/CryptoExchange.Net/Converters/SystemTextJson/DateTimeConverter.cs
@@ -154,6 +154,8 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
if (double.TryParse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var doubleValue))
{
// Parse 1637745563.000 format
+ if (doubleValue <= 0)
+ return default;
if (doubleValue < 19999999999)
return ConvertFromSeconds(doubleValue);
if (doubleValue < 19999999999999)
diff --git a/CryptoExchange.Net/Converters/SystemTextJson/SystemTextJsonMessageAccessor.cs b/CryptoExchange.Net/Converters/SystemTextJson/SystemTextJsonMessageAccessor.cs
index 9ab3b10..f0ee044 100644
--- a/CryptoExchange.Net/Converters/SystemTextJson/SystemTextJsonMessageAccessor.cs
+++ b/CryptoExchange.Net/Converters/SystemTextJson/SystemTextJsonMessageAccessor.cs
@@ -68,6 +68,11 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
var info = $"Deserialize JsonException: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
return new CallResult(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
}
+ catch (Exception ex)
+ {
+ var info = $"Unknown exception: {ex.Message}";
+ return new CallResult(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
+ }
}
///
diff --git a/CryptoExchange.Net/Objects/Sockets/DataEvent.cs b/CryptoExchange.Net/Objects/Sockets/DataEvent.cs
index bfd9db3..d627fcc 100644
--- a/CryptoExchange.Net/Objects/Sockets/DataEvent.cs
+++ b/CryptoExchange.Net/Objects/Sockets/DataEvent.cs
@@ -14,9 +14,14 @@ namespace CryptoExchange.Net.Objects.Sockets
public DateTime Timestamp { get; set; }
///
- /// The topic of the update, what symbol/asset etc..
+ /// The stream producing the update
///
- public string? Topic { get; set; }
+ public string? StreamId { get; set; }
+
+ ///
+ /// The symbol the update is for
+ ///
+ public string? Symbol { get; set; }
///
/// The original data that was received, only available when OutputOriginalData is set to true in the client options
@@ -33,10 +38,11 @@ namespace CryptoExchange.Net.Objects.Sockets
///
public T Data { get; set; }
- internal DataEvent(T data, string? topic, string? originalData, DateTime timestamp, SocketUpdateType? updateType)
+ internal DataEvent(T data, string? streamId, string? symbol, string? originalData, DateTime timestamp, SocketUpdateType? updateType)
{
Data = data;
- Topic = topic;
+ StreamId = streamId;
+ Symbol = symbol;
OriginalData = originalData;
Timestamp = timestamp;
UpdateType = updateType;
@@ -50,7 +56,7 @@ namespace CryptoExchange.Net.Objects.Sockets
///
public DataEvent As(K data)
{
- return new DataEvent(data, Topic, OriginalData, Timestamp, UpdateType);
+ return new DataEvent(data, StreamId, Symbol, OriginalData, Timestamp, UpdateType);
}
///
@@ -58,11 +64,11 @@ namespace CryptoExchange.Net.Objects.Sockets
///
/// The type of the new data
/// The new data
- /// The new topic
+ /// The new symbol
///
- public DataEvent As(K data, string? topic)
+ public DataEvent As(K data, string? symbol)
{
- return new DataEvent(data, topic, OriginalData, Timestamp, UpdateType);
+ return new DataEvent(data, StreamId, symbol, OriginalData, Timestamp, UpdateType);
}
///
@@ -70,12 +76,73 @@ namespace CryptoExchange.Net.Objects.Sockets
///
/// The type of the new data
/// The new data
- /// The new topic
+ /// The new stream id
+ /// The new symbol
/// The type of update
///
- public DataEvent As(K data, string? topic, SocketUpdateType updateType)
+ public DataEvent As(K data, string streamId, string? symbol, SocketUpdateType updateType)
{
- return new DataEvent(data, topic, OriginalData, Timestamp, updateType);
+ return new DataEvent(data, streamId, symbol, OriginalData, Timestamp, updateType);
+ }
+
+ ///
+ /// Specify the symbol
+ ///
+ ///
+ ///
+ public DataEvent WithSymbol(string symbol)
+ {
+ Symbol = symbol;
+ return this;
+ }
+
+ ///
+ /// Specify the update type
+ ///
+ ///
+ ///
+ public DataEvent WithUpdateType(SocketUpdateType type)
+ {
+ UpdateType = type;
+ return this;
+ }
+
+ ///
+ /// Specify the stream id
+ ///
+ ///
+ ///
+ public DataEvent WithStreamId(string streamId)
+ {
+ StreamId = streamId;
+ return this;
+ }
+
+ ///
+ /// Create a CallResult from this DataEvent
+ ///
+ ///
+ public CallResult ToCallResult()
+ {
+ return new CallResult(Data, OriginalData, null);
+ }
+
+ ///
+ /// Create a CallResult from this DataEvent
+ ///
+ ///
+ public CallResult ToCallResult(K data)
+ {
+ return new CallResult(data, OriginalData, null);
+ }
+
+ ///
+ /// Create a CallResult from this DataEvent
+ ///
+ ///
+ public CallResult ToCallResult(Error error)
+ {
+ return new CallResult(default, OriginalData, error);
}
}
}
diff --git a/CryptoExchange.Net/Requests/RequestFactory.cs b/CryptoExchange.Net/Requests/RequestFactory.cs
index b909870..689bcc5 100644
--- a/CryptoExchange.Net/Requests/RequestFactory.cs
+++ b/CryptoExchange.Net/Requests/RequestFactory.cs
@@ -19,6 +19,7 @@ namespace CryptoExchange.Net.Requests
if (client == null)
{
var handler = new HttpClientHandler();
+ handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
if (proxy != null)
{
handler.Proxy = new WebProxy
diff --git a/CryptoExchange.Net/Sockets/Query.cs b/CryptoExchange.Net/Sockets/Query.cs
index 2c9148c..149c17d 100644
--- a/CryptoExchange.Net/Sockets/Query.cs
+++ b/CryptoExchange.Net/Sockets/Query.cs
@@ -1,6 +1,7 @@
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Sockets;
+using CryptoExchange.Net.Requests;
using System;
using System.Collections.Generic;
using System.Threading;
@@ -145,16 +146,17 @@ namespace CryptoExchange.Net.Sockets
///
/// Query
///
- /// Response object type
- public abstract class Query : Query
+ /// The type returned from the server
+ /// The type to be returned to the caller
+ public abstract class Query : Query
{
///
- public override Type? GetMessageType(IMessageAccessor message) => typeof(TResponse);
+ public override Type? GetMessageType(IMessageAccessor message) => typeof(TServerResponse);
///
/// The typed call result
///
- public CallResult? TypedResult => (CallResult?)Result;
+ public CallResult? TypedResult => (CallResult?)Result;
///
/// ctor
@@ -171,7 +173,7 @@ namespace CryptoExchange.Net.Sockets
{
Completed = true;
Response = message.Data;
- Result = HandleMessage(connection, message.As((TResponse)message.Data));
+ Result = HandleMessage(connection, message.As((TServerResponse)message.Data));
_event.Set();
ContinueAwaiter?.WaitOne();
return Result;
@@ -183,7 +185,7 @@ namespace CryptoExchange.Net.Sockets
///
///
///
- public virtual CallResult HandleMessage(SocketConnection connection, DataEvent message) => new CallResult(message.Data, message.OriginalData, null);
+ public abstract CallResult HandleMessage(SocketConnection connection, DataEvent message);
///
public override void Timeout()
@@ -192,7 +194,7 @@ namespace CryptoExchange.Net.Sockets
return;
Completed = true;
- Result = new CallResult(new CancellationRequestedError(null, "Query timeout", null));
+ Result = new CallResult(new CancellationRequestedError(null, "Query timeout", null));
ContinueAwaiter?.Set();
_event.Set();
}
@@ -200,10 +202,35 @@ namespace CryptoExchange.Net.Sockets
///
public override void Fail(Error error)
{
- Result = new CallResult(error);
+ Result = new CallResult(error);
Completed = true;
ContinueAwaiter?.Set();
_event.Set();
}
}
+
+ ///
+ /// Query
+ ///
+ /// Response object type
+ public abstract class Query : Query
+ {
+ ///
+ /// ctor
+ ///
+ ///
+ ///
+ ///
+ protected Query(object request, bool authenticated, int weight = 1) : base(request, authenticated, weight)
+ {
+ }
+
+ ///
+ /// Handle the query response
+ ///
+ ///
+ ///
+ ///
+ public override CallResult HandleMessage(SocketConnection connection, DataEvent message) => message.ToCallResult();
+ }
}
diff --git a/CryptoExchange.Net/Sockets/SocketConnection.cs b/CryptoExchange.Net/Sockets/SocketConnection.cs
index 5d2aaff..13deab3 100644
--- a/CryptoExchange.Net/Sockets/SocketConnection.cs
+++ b/CryptoExchange.Net/Sockets/SocketConnection.cs
@@ -498,7 +498,7 @@ namespace CryptoExchange.Net.Sockets
try
{
var innerSw = Stopwatch.StartNew();
- processor.Handle(this, new DataEvent