mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-06-07 16:06:15 +00:00
Feature/body uri param split (#203)
* Added support for specifying seperate uri and body parameters * Added support for different message and handling generic types on socket queries * Split DataEvent.Topic into StreamId and Symbol properties * Added support for negative time values parsing * Added some helper methods for converting DataEvent to CallResult * Added support for GZip/Deflate automatic decompressing in the default HttpClient * Updated some testing methods
This commit is contained in:
parent
8080ecccc0
commit
9fcd722991
@ -49,11 +49,11 @@ namespace CryptoExchange.Net.Authentication
|
|||||||
/// <param name="method">The method of the request</param>
|
/// <param name="method">The method of the request</param>
|
||||||
/// <param name="auth">If the requests should be authenticated</param>
|
/// <param name="auth">If the requests should be authenticated</param>
|
||||||
/// <param name="arraySerialization">Array serialization type</param>
|
/// <param name="arraySerialization">Array serialization type</param>
|
||||||
/// <param name="parameterPosition">The position where the providedParameters should go</param>
|
|
||||||
/// <param name="requestBodyFormat">The formatting of the request body</param>
|
/// <param name="requestBodyFormat">The formatting of the request body</param>
|
||||||
/// <param name="uriParameters">Parameters that need to be in the Uri of the request. Should include the provided parameters if they should go in the uri</param>
|
/// <param name="uriParameters">Parameters that need to be in the Uri of the request. Should include the provided parameters if they should go in the uri</param>
|
||||||
/// <param name="bodyParameters">Parameters that need to be in the body of the request. Should include the provided parameters if they should go in the body</param>
|
/// <param name="bodyParameters">Parameters that need to be in the body of the request. Should include the provided parameters if they should go in the body</param>
|
||||||
/// <param name="headers">The headers that should be send with the request</param>
|
/// <param name="headers">The headers that should be send with the request</param>
|
||||||
|
/// <param name="parameterPosition">The position where the providedParameters should go</param>
|
||||||
public abstract void AuthenticateRequest(
|
public abstract void AuthenticateRequest(
|
||||||
RestApiClient apiClient,
|
RestApiClient apiClient,
|
||||||
Uri uri,
|
Uri uri,
|
||||||
|
@ -150,23 +150,60 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// <param name="additionalHeaders">Additional headers for this request</param>
|
/// <param name="additionalHeaders">Additional headers for this request</param>
|
||||||
/// <param name="weight">Override the request weight for this request definition, for example when the weight depends on the parameters</param>
|
/// <param name="weight">Override the request weight for this request definition, for example when the weight depends on the parameters</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual async Task<WebCallResult<T>> SendAsync<T>(
|
protected virtual Task<WebCallResult<T>> SendAsync<T>(
|
||||||
string baseAddress,
|
string baseAddress,
|
||||||
RequestDefinition definition,
|
RequestDefinition definition,
|
||||||
ParameterCollection? parameters,
|
ParameterCollection? parameters,
|
||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
Dictionary<string, string>? additionalHeaders = null,
|
Dictionary<string, string>? additionalHeaders = null,
|
||||||
int? weight = null) where T : class
|
int? weight = null) where T : class
|
||||||
|
{
|
||||||
|
var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
|
||||||
|
return SendAsync<T>(
|
||||||
|
baseAddress,
|
||||||
|
definition,
|
||||||
|
parameterPosition == HttpMethodParameterPosition.InUri ? parameters : null,
|
||||||
|
parameterPosition == HttpMethodParameterPosition.InBody ? parameters : null,
|
||||||
|
cancellationToken,
|
||||||
|
additionalHeaders,
|
||||||
|
weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a request to the base address based on the request definition
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Response type</typeparam>
|
||||||
|
/// <param name="baseAddress">Host and schema</param>
|
||||||
|
/// <param name="definition">Request definition</param>
|
||||||
|
/// <param name="uriParameters">Request query parameters</param>
|
||||||
|
/// <param name="bodyParameters">Request body parameters</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <param name="additionalHeaders">Additional headers for this request</param>
|
||||||
|
/// <param name="weight">Override the request weight for this request definition, for example when the weight depends on the parameters</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected virtual async Task<WebCallResult<T>> SendAsync<T>(
|
||||||
|
string baseAddress,
|
||||||
|
RequestDefinition definition,
|
||||||
|
ParameterCollection? uriParameters,
|
||||||
|
ParameterCollection? bodyParameters,
|
||||||
|
CancellationToken cancellationToken,
|
||||||
|
Dictionary<string, string>? additionalHeaders = null,
|
||||||
|
int? weight = null) where T : class
|
||||||
{
|
{
|
||||||
int currentTry = 0;
|
int currentTry = 0;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
currentTry++;
|
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)
|
if (!prepareResult)
|
||||||
return new WebCallResult<T>(prepareResult.Error!);
|
return new WebCallResult<T>(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)}]")));
|
_logger.RestApiSendRequest(request.RequestId, definition, request.Content, request.Uri.Query, string.Join(", ", request.GetHeaders().Select(h => h.Key + $"=[{string.Join(",", h.Value)}]")));
|
||||||
TotalRequestsMade++;
|
TotalRequestsMade++;
|
||||||
var result = await GetResponseAsync<T>(request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false);
|
var result = await GetResponseAsync<T>(request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false);
|
||||||
@ -187,7 +224,6 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="baseAddress">Host and schema</param>
|
/// <param name="baseAddress">Host and schema</param>
|
||||||
/// <param name="definition">Request definition</param>
|
/// <param name="definition">Request definition</param>
|
||||||
/// <param name="parameters">Request parameters</param>
|
|
||||||
/// <param name="cancellationToken">Cancellation token</param>
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
/// <param name="additionalHeaders">Additional headers for this request</param>
|
/// <param name="additionalHeaders">Additional headers for this request</param>
|
||||||
/// <param name="weight">Override the request weight for this request</param>
|
/// <param name="weight">Override the request weight for this request</param>
|
||||||
@ -196,7 +232,6 @@ namespace CryptoExchange.Net.Clients
|
|||||||
protected virtual async Task<CallResult> PrepareAsync(
|
protected virtual async Task<CallResult> PrepareAsync(
|
||||||
string baseAddress,
|
string baseAddress,
|
||||||
RequestDefinition definition,
|
RequestDefinition definition,
|
||||||
ParameterCollection? parameters,
|
|
||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
Dictionary<string, string>? additionalHeaders = null,
|
Dictionary<string, string>? additionalHeaders = null,
|
||||||
int? weight = null)
|
int? weight = null)
|
||||||
@ -264,25 +299,27 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="baseAddress">Host and schema</param>
|
/// <param name="baseAddress">Host and schema</param>
|
||||||
/// <param name="definition">Request definition</param>
|
/// <param name="definition">Request definition</param>
|
||||||
/// <param name="parameters">The parameters of the request</param>
|
/// <param name="uriParameters">The query parameters of the request</param>
|
||||||
|
/// <param name="bodyParameters">The body parameters of the request</param>
|
||||||
/// <param name="additionalHeaders">Additional headers to send with the request</param>
|
/// <param name="additionalHeaders">Additional headers to send with the request</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual IRequest CreateRequest(
|
protected virtual IRequest CreateRequest(
|
||||||
string baseAddress,
|
string baseAddress,
|
||||||
RequestDefinition definition,
|
RequestDefinition definition,
|
||||||
ParameterCollection? parameters,
|
ParameterCollection? uriParameters,
|
||||||
|
ParameterCollection? bodyParameters,
|
||||||
Dictionary<string, string>? additionalHeaders)
|
Dictionary<string, string>? 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 uri = new Uri(baseAddress.AppendPath(definition.Path));
|
||||||
var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
|
|
||||||
var arraySerialization = definition.ArraySerialization ?? ArraySerialization;
|
var arraySerialization = definition.ArraySerialization ?? ArraySerialization;
|
||||||
var bodyFormat = definition.RequestBodyFormat ?? RequestBodyFormat;
|
var bodyFormat = definition.RequestBodyFormat ?? RequestBodyFormat;
|
||||||
var requestId = ExchangeHelpers.NextId();
|
var requestId = ExchangeHelpers.NextId();
|
||||||
|
var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
|
||||||
|
|
||||||
var headers = new Dictionary<string, string>();
|
var headers = new Dictionary<string, string>();
|
||||||
var uriParameters = parameterPosition == HttpMethodParameterPosition.InUri ? CreateParameterDictionary(parameters) : new Dictionary<string, object>();
|
|
||||||
var bodyParameters = parameterPosition == HttpMethodParameterPosition.InBody ? CreateParameterDictionary(parameters) : new Dictionary<string, object>();
|
|
||||||
if (AuthenticationProvider != null)
|
if (AuthenticationProvider != null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -291,13 +328,14 @@ namespace CryptoExchange.Net.Clients
|
|||||||
this,
|
this,
|
||||||
uri,
|
uri,
|
||||||
definition.Method,
|
definition.Method,
|
||||||
uriParameters,
|
uriParams,
|
||||||
bodyParameters,
|
bodyParams,
|
||||||
headers,
|
headers,
|
||||||
definition.Authenticated,
|
definition.Authenticated,
|
||||||
arraySerialization,
|
arraySerialization,
|
||||||
parameterPosition,
|
parameterPosition,
|
||||||
bodyFormat);
|
bodyFormat
|
||||||
|
);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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
|
// 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);
|
var request = RequestFactory.Create(definition.Method, uri, requestId);
|
||||||
request.Accept = Constants.JsonContentHeader;
|
request.Accept = Constants.JsonContentHeader;
|
||||||
@ -343,8 +371,8 @@ namespace CryptoExchange.Net.Clients
|
|||||||
if (parameterPosition == HttpMethodParameterPosition.InBody)
|
if (parameterPosition == HttpMethodParameterPosition.InBody)
|
||||||
{
|
{
|
||||||
var contentType = bodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
|
var contentType = bodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
|
||||||
if (bodyParameters.Count != 0)
|
if (bodyParams.Count != 0)
|
||||||
WriteParamBody(request, bodyParameters, contentType);
|
WriteParamBody(request, bodyParams, contentType);
|
||||||
else
|
else
|
||||||
request.SetContent(RequestBodyEmptyContent, contentType);
|
request.SetContent(RequestBodyEmptyContent, contentType);
|
||||||
}
|
}
|
||||||
@ -739,7 +767,8 @@ namespace CryptoExchange.Net.Clients
|
|||||||
signed,
|
signed,
|
||||||
arraySerialization,
|
arraySerialization,
|
||||||
parameterPosition,
|
parameterPosition,
|
||||||
bodyFormat);
|
bodyFormat
|
||||||
|
);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -278,10 +278,11 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send a query on a socket connection to the BaseAddress and wait for the response
|
/// Send a query on a socket connection to the BaseAddress and wait for the response
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">Expected result type</typeparam>
|
/// <typeparam name="THandlerResponse">Expected result type</typeparam>
|
||||||
|
/// <typeparam name="TServerResponse">The type returned to the caller</typeparam>
|
||||||
/// <param name="query">The query</param>
|
/// <param name="query">The query</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual Task<CallResult<T>> QueryAsync<T>(Query<T> query)
|
protected virtual Task<CallResult<THandlerResponse>> QueryAsync<TServerResponse, THandlerResponse>(Query<TServerResponse, THandlerResponse> query)
|
||||||
{
|
{
|
||||||
return QueryAsync(BaseAddress, query);
|
return QueryAsync(BaseAddress, query);
|
||||||
}
|
}
|
||||||
@ -289,14 +290,15 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send a query on a socket connection and wait for the response
|
/// Send a query on a socket connection and wait for the response
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The expected result type</typeparam>
|
/// <typeparam name="THandlerResponse">Expected result type</typeparam>
|
||||||
|
/// <typeparam name="TServerResponse">The type returned to the caller</typeparam>
|
||||||
/// <param name="url">The url for the request</param>
|
/// <param name="url">The url for the request</param>
|
||||||
/// <param name="query">The query</param>
|
/// <param name="query">The query</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual async Task<CallResult<T>> QueryAsync<T>(string url, Query<T> query)
|
protected virtual async Task<CallResult<THandlerResponse>> QueryAsync<TServerResponse, THandlerResponse>(string url, Query<TServerResponse, THandlerResponse> query)
|
||||||
{
|
{
|
||||||
if (_disposing)
|
if (_disposing)
|
||||||
return new CallResult<T>(new InvalidOperationError("Client disposed, can't query"));
|
return new CallResult<THandlerResponse>(new InvalidOperationError("Client disposed, can't query"));
|
||||||
|
|
||||||
SocketConnection socketConnection;
|
SocketConnection socketConnection;
|
||||||
var released = false;
|
var released = false;
|
||||||
@ -305,7 +307,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
{
|
{
|
||||||
var socketResult = await GetSocketConnection(url, query.Authenticated).ConfigureAwait(false);
|
var socketResult = await GetSocketConnection(url, query.Authenticated).ConfigureAwait(false);
|
||||||
if (!socketResult)
|
if (!socketResult)
|
||||||
return socketResult.As<T>(default);
|
return socketResult.As<THandlerResponse>(default);
|
||||||
|
|
||||||
socketConnection = socketResult.Data;
|
socketConnection = socketResult.Data;
|
||||||
|
|
||||||
@ -318,7 +320,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
|
|
||||||
var connectResult = await ConnectIfNeededAsync(socketConnection, query.Authenticated).ConfigureAwait(false);
|
var connectResult = await ConnectIfNeededAsync(socketConnection, query.Authenticated).ConfigureAwait(false);
|
||||||
if (!connectResult)
|
if (!connectResult)
|
||||||
return new CallResult<T>(connectResult.Error!);
|
return new CallResult<THandlerResponse>(connectResult.Error!);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -329,10 +331,10 @@ namespace CryptoExchange.Net.Clients
|
|||||||
if (socketConnection.PausedActivity)
|
if (socketConnection.PausedActivity)
|
||||||
{
|
{
|
||||||
_logger.HasBeenPausedCantSendQueryAtThisMoment(socketConnection.SocketId);
|
_logger.HasBeenPausedCantSendQueryAtThisMoment(socketConnection.SocketId);
|
||||||
return new CallResult<T>(new ServerError("Socket is paused"));
|
return new CallResult<THandlerResponse>(new ServerError("Socket is paused"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return await socketConnection.SendAndWaitQueryAsync(query).ConfigureAwait(false);
|
return await socketConnection.SendAndWaitQueryAsync<TServerResponse, THandlerResponse>(query).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -154,6 +154,8 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
if (double.TryParse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var doubleValue))
|
if (double.TryParse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var doubleValue))
|
||||||
{
|
{
|
||||||
// Parse 1637745563.000 format
|
// Parse 1637745563.000 format
|
||||||
|
if (doubleValue <= 0)
|
||||||
|
return default;
|
||||||
if (doubleValue < 19999999999)
|
if (doubleValue < 19999999999)
|
||||||
return ConvertFromSeconds(doubleValue);
|
return ConvertFromSeconds(doubleValue);
|
||||||
if (doubleValue < 19999999999999)
|
if (doubleValue < 19999999999999)
|
||||||
|
@ -68,6 +68,11 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
var info = $"Deserialize JsonException: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
|
var info = $"Deserialize JsonException: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
|
||||||
return new CallResult<T>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
|
return new CallResult<T>(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<T>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -14,9 +14,14 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
public DateTime Timestamp { get; set; }
|
public DateTime Timestamp { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The topic of the update, what symbol/asset etc..
|
/// The stream producing the update
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Topic { get; set; }
|
public string? StreamId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The symbol the update is for
|
||||||
|
/// </summary>
|
||||||
|
public string? Symbol { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The original data that was received, only available when OutputOriginalData is set to true in the client options
|
/// 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
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public T Data { get; set; }
|
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;
|
Data = data;
|
||||||
Topic = topic;
|
StreamId = streamId;
|
||||||
|
Symbol = symbol;
|
||||||
OriginalData = originalData;
|
OriginalData = originalData;
|
||||||
Timestamp = timestamp;
|
Timestamp = timestamp;
|
||||||
UpdateType = updateType;
|
UpdateType = updateType;
|
||||||
@ -50,7 +56,7 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public DataEvent<K> As<K>(K data)
|
public DataEvent<K> As<K>(K data)
|
||||||
{
|
{
|
||||||
return new DataEvent<K>(data, Topic, OriginalData, Timestamp, UpdateType);
|
return new DataEvent<K>(data, StreamId, Symbol, OriginalData, Timestamp, UpdateType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -58,11 +64,11 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="K">The type of the new data</typeparam>
|
/// <typeparam name="K">The type of the new data</typeparam>
|
||||||
/// <param name="data">The new data</param>
|
/// <param name="data">The new data</param>
|
||||||
/// <param name="topic">The new topic</param>
|
/// <param name="symbol">The new symbol</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public DataEvent<K> As<K>(K data, string? topic)
|
public DataEvent<K> As<K>(K data, string? symbol)
|
||||||
{
|
{
|
||||||
return new DataEvent<K>(data, topic, OriginalData, Timestamp, UpdateType);
|
return new DataEvent<K>(data, StreamId, symbol, OriginalData, Timestamp, UpdateType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -70,12 +76,73 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="K">The type of the new data</typeparam>
|
/// <typeparam name="K">The type of the new data</typeparam>
|
||||||
/// <param name="data">The new data</param>
|
/// <param name="data">The new data</param>
|
||||||
/// <param name="topic">The new topic</param>
|
/// <param name="streamId">The new stream id</param>
|
||||||
|
/// <param name="symbol">The new symbol</param>
|
||||||
/// <param name="updateType">The type of update</param>
|
/// <param name="updateType">The type of update</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public DataEvent<K> As<K>(K data, string? topic, SocketUpdateType updateType)
|
public DataEvent<K> As<K>(K data, string streamId, string? symbol, SocketUpdateType updateType)
|
||||||
{
|
{
|
||||||
return new DataEvent<K>(data, topic, OriginalData, Timestamp, updateType);
|
return new DataEvent<K>(data, streamId, symbol, OriginalData, Timestamp, updateType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specify the symbol
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="symbol"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public DataEvent<T> WithSymbol(string symbol)
|
||||||
|
{
|
||||||
|
Symbol = symbol;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specify the update type
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public DataEvent<T> WithUpdateType(SocketUpdateType type)
|
||||||
|
{
|
||||||
|
UpdateType = type;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specify the stream id
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="streamId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public DataEvent<T> WithStreamId(string streamId)
|
||||||
|
{
|
||||||
|
StreamId = streamId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a CallResult from this DataEvent
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public CallResult<T> ToCallResult()
|
||||||
|
{
|
||||||
|
return new CallResult<T>(Data, OriginalData, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a CallResult from this DataEvent
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public CallResult<K> ToCallResult<K>(K data)
|
||||||
|
{
|
||||||
|
return new CallResult<K>(data, OriginalData, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a CallResult from this DataEvent
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public CallResult<K> ToCallResult<K>(Error error)
|
||||||
|
{
|
||||||
|
return new CallResult<K>(default, OriginalData, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ namespace CryptoExchange.Net.Requests
|
|||||||
if (client == null)
|
if (client == null)
|
||||||
{
|
{
|
||||||
var handler = new HttpClientHandler();
|
var handler = new HttpClientHandler();
|
||||||
|
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
|
||||||
if (proxy != null)
|
if (proxy != null)
|
||||||
{
|
{
|
||||||
handler.Proxy = new WebProxy
|
handler.Proxy = new WebProxy
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using CryptoExchange.Net.Interfaces;
|
using CryptoExchange.Net.Interfaces;
|
||||||
using CryptoExchange.Net.Objects;
|
using CryptoExchange.Net.Objects;
|
||||||
using CryptoExchange.Net.Objects.Sockets;
|
using CryptoExchange.Net.Objects.Sockets;
|
||||||
|
using CryptoExchange.Net.Requests;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -145,16 +146,17 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Query
|
/// Query
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TResponse">Response object type</typeparam>
|
/// <typeparam name="TServerResponse">The type returned from the server</typeparam>
|
||||||
public abstract class Query<TResponse> : Query
|
/// <typeparam name="THandlerResponse">The type to be returned to the caller</typeparam>
|
||||||
|
public abstract class Query<TServerResponse, THandlerResponse> : Query
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override Type? GetMessageType(IMessageAccessor message) => typeof(TResponse);
|
public override Type? GetMessageType(IMessageAccessor message) => typeof(TServerResponse);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The typed call result
|
/// The typed call result
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CallResult<TResponse>? TypedResult => (CallResult<TResponse>?)Result;
|
public CallResult<THandlerResponse>? TypedResult => (CallResult<THandlerResponse>?)Result;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
@ -171,7 +173,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
{
|
{
|
||||||
Completed = true;
|
Completed = true;
|
||||||
Response = message.Data;
|
Response = message.Data;
|
||||||
Result = HandleMessage(connection, message.As((TResponse)message.Data));
|
Result = HandleMessage(connection, message.As((TServerResponse)message.Data));
|
||||||
_event.Set();
|
_event.Set();
|
||||||
ContinueAwaiter?.WaitOne();
|
ContinueAwaiter?.WaitOne();
|
||||||
return Result;
|
return Result;
|
||||||
@ -183,7 +185,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// <param name="connection"></param>
|
/// <param name="connection"></param>
|
||||||
/// <param name="message"></param>
|
/// <param name="message"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public virtual CallResult<TResponse> HandleMessage(SocketConnection connection, DataEvent<TResponse> message) => new CallResult<TResponse>(message.Data, message.OriginalData, null);
|
public abstract CallResult<THandlerResponse> HandleMessage(SocketConnection connection, DataEvent<TServerResponse> message);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Timeout()
|
public override void Timeout()
|
||||||
@ -192,7 +194,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
Completed = true;
|
Completed = true;
|
||||||
Result = new CallResult<TResponse>(new CancellationRequestedError(null, "Query timeout", null));
|
Result = new CallResult<THandlerResponse>(new CancellationRequestedError(null, "Query timeout", null));
|
||||||
ContinueAwaiter?.Set();
|
ContinueAwaiter?.Set();
|
||||||
_event.Set();
|
_event.Set();
|
||||||
}
|
}
|
||||||
@ -200,10 +202,35 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Fail(Error error)
|
public override void Fail(Error error)
|
||||||
{
|
{
|
||||||
Result = new CallResult<TResponse>(error);
|
Result = new CallResult<THandlerResponse>(error);
|
||||||
Completed = true;
|
Completed = true;
|
||||||
ContinueAwaiter?.Set();
|
ContinueAwaiter?.Set();
|
||||||
_event.Set();
|
_event.Set();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TResponse">Response object type</typeparam>
|
||||||
|
public abstract class Query<TResponse> : Query<TResponse, TResponse>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <param name="authenticated"></param>
|
||||||
|
/// <param name="weight"></param>
|
||||||
|
protected Query(object request, bool authenticated, int weight = 1) : base(request, authenticated, weight)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle the query response
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connection"></param>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override CallResult<TResponse> HandleMessage(SocketConnection connection, DataEvent<TResponse> message) => message.ToCallResult();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -498,7 +498,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var innerSw = Stopwatch.StartNew();
|
var innerSw = Stopwatch.StartNew();
|
||||||
processor.Handle(this, new DataEvent<object>(deserialized, null, originalData, receiveTime, null));
|
processor.Handle(this, new DataEvent<object>(deserialized, null, null, originalData, receiveTime, null));
|
||||||
totalUserTime += (int)innerSw.ElapsedMilliseconds;
|
totalUserTime += (int)innerSw.ElapsedMilliseconds;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -696,14 +696,15 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send a query request and wait for an answer
|
/// Send a query request and wait for an answer
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">Query response type</typeparam>
|
/// <typeparam name="THandlerResponse">Expected result type</typeparam>
|
||||||
|
/// <typeparam name="TServerResponse">The type returned to the caller</typeparam>
|
||||||
/// <param name="query">Query to send</param>
|
/// <param name="query">Query to send</param>
|
||||||
/// <param name="continueEvent">Wait event for when the socket message handler can continue</param>
|
/// <param name="continueEvent">Wait event for when the socket message handler can continue</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public virtual async Task<CallResult<T>> SendAndWaitQueryAsync<T>(Query<T> query, ManualResetEvent? continueEvent = null)
|
public virtual async Task<CallResult<THandlerResponse>> SendAndWaitQueryAsync<TServerResponse, THandlerResponse>(Query<TServerResponse, THandlerResponse> query, ManualResetEvent? continueEvent = null)
|
||||||
{
|
{
|
||||||
await SendAndWaitIntAsync(query, continueEvent).ConfigureAwait(false);
|
await SendAndWaitIntAsync(query, continueEvent).ConfigureAwait(false);
|
||||||
return query.TypedResult ?? new CallResult<T>(new ServerError("Timeout"));
|
return query.TypedResult ?? new CallResult<THandlerResponse>(new ServerError("Timeout"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SendAndWaitIntAsync(Query query, ManualResetEvent? continueEvent)
|
private async Task SendAndWaitIntAsync(Query query, ManualResetEvent? continueEvent)
|
||||||
|
@ -52,8 +52,13 @@ namespace CryptoExchange.Net.Testing.Comparers
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (dict[dictProp.Name] == default && dictProp.Value.Type != JTokenType.Null)
|
if (dict[dictProp.Name] == default && dictProp.Value.Type != JTokenType.Null)
|
||||||
|
{
|
||||||
|
if (dictProp.Value.ToString() == "")
|
||||||
|
continue;
|
||||||
|
|
||||||
// Property value not correct
|
// Property value not correct
|
||||||
throw new Exception($"{method}: Dictionary entry `{dictProp.Name}` has no value while input json has value {dictProp.Value}");
|
throw new Exception($"{method}: Dictionary entry `{dictProp.Name}` has no value while input json has value {dictProp.Value}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,7 +167,7 @@ namespace CryptoExchange.Net.Testing.Comparers
|
|||||||
|
|
||||||
if (dictProp.Value.Type == JTokenType.Object)
|
if (dictProp.Value.Type == JTokenType.Object)
|
||||||
{
|
{
|
||||||
CheckObject(method, dictProp, dict[dictProp.Name]!, ignoreProperties);
|
CheckPropertyValue(method, dictProp.Value, dict[dictProp.Name]!, dict[dictProp.Name].GetType(), null, null, ignoreProperties);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -180,7 +185,10 @@ namespace CryptoExchange.Net.Testing.Comparers
|
|||||||
var enumerator = list.GetEnumerator();
|
var enumerator = list.GetEnumerator();
|
||||||
foreach (JToken jtoken in jObjs)
|
foreach (JToken jtoken in jObjs)
|
||||||
{
|
{
|
||||||
enumerator.MoveNext();
|
var moved = enumerator.MoveNext();
|
||||||
|
if (!moved)
|
||||||
|
throw new Exception("Enumeration not moved; incorrect amount of results?");
|
||||||
|
|
||||||
var typeConverter = enumerator.Current.GetType().GetCustomAttributes(typeof(JsonConverterAttribute), true);
|
var typeConverter = enumerator.Current.GetType().GetCustomAttributes(typeof(JsonConverterAttribute), true);
|
||||||
if (typeConverter.Length != 0 && ((JsonConverterAttribute)typeConverter.First()).ConverterType != typeof(ArrayConverter))
|
if (typeConverter.Length != 0 && ((JsonConverterAttribute)typeConverter.First()).ConverterType != typeof(ArrayConverter))
|
||||||
// Custom converter for the type, skip
|
// Custom converter for the type, skip
|
||||||
@ -260,9 +268,9 @@ namespace CryptoExchange.Net.Testing.Comparers
|
|||||||
else if (objectValue is DateTime time)
|
else if (objectValue is DateTime time)
|
||||||
{
|
{
|
||||||
if (time != DateTimeConverter.ParseFromString(jsonValue.Value<string>()!))
|
if (time != DateTimeConverter.ParseFromString(jsonValue.Value<string>()!))
|
||||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<decimal>()} vs {time}");
|
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<string>()} vs {time}");
|
||||||
}
|
}
|
||||||
else if (propertyType.IsEnum)
|
else if (propertyType.IsEnum || Nullable.GetUnderlyingType(propertyType)?.IsEnum == true)
|
||||||
{
|
{
|
||||||
// TODO enum comparing
|
// TODO enum comparing
|
||||||
}
|
}
|
||||||
@ -278,6 +286,10 @@ namespace CryptoExchange.Net.Testing.Comparers
|
|||||||
if (time != DateTimeConverter.ParseFromDouble(jsonValue.Value<long>()!))
|
if (time != DateTimeConverter.ParseFromDouble(jsonValue.Value<long>()!))
|
||||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<decimal>()} vs {time}");
|
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<decimal>()} vs {time}");
|
||||||
}
|
}
|
||||||
|
else if (propertyType.IsEnum || Nullable.GetUnderlyingType(propertyType)?.IsEnum == true)
|
||||||
|
{
|
||||||
|
// TODO enum comparing
|
||||||
|
}
|
||||||
else if (jsonValue.Value<long>() != Convert.ToInt64(objectValue))
|
else if (jsonValue.Value<long>() != Convert.ToInt64(objectValue))
|
||||||
{
|
{
|
||||||
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<long>()} vs {Convert.ToInt64(objectValue)}");
|
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<long>()} vs {Convert.ToInt64(objectValue)}");
|
||||||
|
@ -109,6 +109,7 @@ namespace CryptoExchange.Net.Testing
|
|||||||
if (lastMessage == null)
|
if (lastMessage == null)
|
||||||
throw new Exception($"{name} expected to {line} to be send to server but did not receive anything");
|
throw new Exception($"{name} expected to {line} to be send to server but did not receive anything");
|
||||||
|
|
||||||
|
|
||||||
var lastMessageJson = JToken.Parse(lastMessage);
|
var lastMessageJson = JToken.Parse(lastMessage);
|
||||||
var expectedJson = JToken.Parse(line.Substring(2));
|
var expectedJson = JToken.Parse(line.Substring(2));
|
||||||
foreach(var item in expectedJson)
|
foreach(var item in expectedJson)
|
||||||
@ -121,6 +122,12 @@ namespace CryptoExchange.Net.Testing
|
|||||||
overrideKey = val.ToString();
|
overrideKey = val.ToString();
|
||||||
overrideValue = lastMessageJson[prop.Name]?.Value<string>();
|
overrideValue = lastMessageJson[prop.Name]?.Value<string>();
|
||||||
}
|
}
|
||||||
|
else if (val.ToString() == "-999")
|
||||||
|
{
|
||||||
|
// -999 value is used to replace parts or response messages
|
||||||
|
overrideKey = val.ToString();
|
||||||
|
overrideValue = lastMessageJson[prop.Name]?.Value<decimal>().ToString();
|
||||||
|
}
|
||||||
else if (lastMessageJson[prop.Name]?.Value<string>() != val.ToString() && ignoreProperties?.Contains(prop.Name) != true)
|
else if (lastMessageJson[prop.Name]?.Value<string>() != val.ToString() && ignoreProperties?.Contains(prop.Name) != true)
|
||||||
throw new Exception($"{name} Expected {prop.Name} to be {val}, but was {lastMessageJson[prop.Name]?.Value<string>()}");
|
throw new Exception($"{name} Expected {prop.Name} to be {val}, but was {lastMessageJson[prop.Name]?.Value<string>()}");
|
||||||
}
|
}
|
||||||
|
@ -136,8 +136,9 @@ namespace CryptoExchange.Net.Testing
|
|||||||
headers,
|
headers,
|
||||||
true,
|
true,
|
||||||
client.ArraySerialization,
|
client.ArraySerialization,
|
||||||
client.ParameterPositions[method],
|
client.ParameterPositions[method],
|
||||||
client.RequestBodyFormat);
|
client.RequestBodyFormat
|
||||||
|
);
|
||||||
|
|
||||||
var signature = getSignature(uriParams, bodyParams, headers);
|
var signature = getSignature(uriParams, bodyParams, headers);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user