1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-06-11 01:46:12 +00:00
This commit is contained in:
JKorf 2023-10-31 21:28:53 +01:00
parent cf941fe5c9
commit 5539320827
5 changed files with 103 additions and 166 deletions

View File

@ -8,12 +8,27 @@ using System.Text;
namespace CryptoExchange.Net.Converters namespace CryptoExchange.Net.Converters
{ {
/// <summary>
/// Socket message converter
/// </summary>
public abstract class SocketConverter public abstract class SocketConverter
{ {
/// <summary>
/// Fields to use for the message subscription identifier
/// </summary>
public virtual string[]? SubscriptionIdFields => null; public virtual string[]? SubscriptionIdFields => null;
/// <summary>
/// Fields to use for the message type identifier
/// </summary>
public abstract string[] TypeIdFields { get; } public abstract string[] TypeIdFields { get; }
public abstract Type? GetDeserializationType(Dictionary<string, string> idValues, List<MessageListener> listeners); /// <summary>
/// Return the type of object that the message should be parsed to based on the type id values dictionary
/// </summary>
/// <param name="idValues"></param>
/// <param name="listeners"></param>
/// <returns></returns>
public abstract Type? GetDeserializationType(Dictionary<string, string?> idValues, List<MessageListener> listeners);
/// <inheritdoc /> /// <inheritdoc />
public ParsedMessage? ReadJson(Stream stream, List<MessageListener> listeners, bool outputOriginalData) public ParsedMessage? ReadJson(Stream stream, List<MessageListener> listeners, bool outputOriginalData)
@ -37,8 +52,9 @@ namespace CryptoExchange.Net.Converters
{ {
token = JToken.Load(jsonTextReader); token = JToken.Load(jsonTextReader);
} }
catch(Exception ex) catch(Exception)
{ {
// Not a json message
return null; return null;
} }
@ -48,62 +64,47 @@ namespace CryptoExchange.Net.Converters
token = token.First!; token = token.First!;
} }
var typeIdDict = new Dictionary<string, string>(); var typeIdDict = new Dictionary<string, string?>();
foreach(var idField in TypeIdFields) string idString = "";
foreach (var idField in TypeIdFields)
{ {
var splitTokens = idField.Split(new char[] { ':' }); var val = GetValueForKey(token, idField);
var accessToken = token; idString += val;
foreach (var splitToken in splitTokens) typeIdDict[idField] = val;
{
accessToken = accessToken[splitToken];
if (accessToken == null)
break;
if (accessToken.Type == JTokenType.Array)
{
// Received array, take first item as reference
accessToken = accessToken.First!;
}
}
typeIdDict[idField] = accessToken?.ToString();
} }
string idString = "";
if (SubscriptionIdFields != null) if (SubscriptionIdFields != null)
{ {
idString = "";
foreach (var idField in SubscriptionIdFields) foreach (var idField in SubscriptionIdFields)
{ idString += GetValueForKey(token, idField);
var splitTokens = idField.Split(new char[] { ':' });
var accessToken = token;
foreach (var splitToken in splitTokens)
{
accessToken = accessToken[splitToken];
if (accessToken == null)
break;
}
idString += accessToken?.ToString();
}
}
else
{
foreach (var item in typeIdDict)
idString += item.Value;
} }
result.Identifier = idString; result.Identifier = idString;
var resultType = GetDeserializationType(typeIdDict, listeners); var resultType = GetDeserializationType(typeIdDict, listeners);
result.Data = resultType == null ? null : token.ToObject(resultType); result.Data = resultType == null ? null : token.ToObject(resultType);
return result; return result;
} }
}
public class ParsedMessage private string? GetValueForKey(JToken token, string key)
{ {
public string Identifier { get; set; } = null!; var splitTokens = key.Split(new char[] { ':' });
public string? OriginalData { get; set; } var accessToken = token;
public object? Data { get; set; } foreach (var splitToken in splitTokens)
{
accessToken = accessToken[splitToken];
if (accessToken == null)
break;
if (accessToken.Type == JTokenType.Array)
{
// Received array, take first item as reference
accessToken = accessToken.First!;
}
}
return accessToken?.ToString();
}
} }
} }

View File

@ -0,0 +1,21 @@
namespace CryptoExchange.Net.Objects.Sockets
{
/// <summary>
/// Parsed message object
/// </summary>
public class ParsedMessage
{
/// <summary>
/// Identifier string
/// </summary>
public string Identifier { get; set; } = null!;
/// <summary>
/// Original data if the option is enabled
/// </summary>
public string? OriginalData { get; set; }
/// <summary>
/// Parsed data object
/// </summary>
public object? Data { get; set; }
}
}

View File

@ -322,14 +322,13 @@ namespace CryptoExchange.Net.Sockets
protected virtual async Task HandleStreamMessage(Stream stream) protected virtual async Task HandleStreamMessage(Stream stream)
{ {
var timestamp = DateTime.UtcNow; var timestamp = DateTime.UtcNow;
//var streamMessage = new StreamMessage(this, stream, timestamp);
TimeSpan userCodeDuration = TimeSpan.Zero; TimeSpan userCodeDuration = TimeSpan.Zero;
List<MessageListener> listeners; List<MessageListener> listeners;
lock (_listenerLock) lock (_listenerLock)
listeners = _messageListeners.OrderByDescending(x => x.Priority).ToList(); listeners = _messageListeners.OrderByDescending(x => x.Priority).ToList();
var result = ApiClient.StreamConverter.ReadJson(stream, listeners.OfType<MessageListener>().ToList(), ApiClient.ApiOptions.OutputOriginalData ?? ApiClient.ClientOptions.OutputOriginalData); // TODO var result = ApiClient.StreamConverter.ReadJson(stream, listeners, ApiClient.ApiOptions.OutputOriginalData ?? ApiClient.ClientOptions.OutputOriginalData); // TODO
if(result == null) if(result == null)
{ {
stream.Position = 0; stream.Position = 0;
@ -358,78 +357,42 @@ namespace CryptoExchange.Net.Sockets
return; return;
} }
foreach (var pendingRequest in _pendingRequests) List<PendingRequest> pendingRequests;
lock (_pendingRequests)
pendingRequests = _pendingRequests.ToList();
foreach (var pendingRequest in pendingRequests)
{ {
if (pendingRequest.MessageMatchesHandler(result)) if (pendingRequest.MessageMatchesHandler(result))
{ {
await pendingRequest.ProcessAsync(result).ConfigureAwait(false); lock (_pendingRequests)
_pendingRequests.Remove(pendingRequest);
if (pendingRequest.Completed)
{
// Answer to a timed out request, unsub if it is a subscription request
if (pendingRequest.MessageListener != null)
{
_logger.Log(LogLevel.Warning, $"Socket {SocketId} Received subscription info after request timed out; unsubscribing. Consider increasing the RequestTimeout");
_ = UnsubscribeAsync(pendingRequest.MessageListener).ConfigureAwait(false);
}
}
else
{
_logger.Log(LogLevel.Trace, $"Socket {SocketId} - msg {pendingRequest.Id} - received data matched to pending request");
await pendingRequest.ProcessAsync(result).ConfigureAwait(false);
}
return; return;
} }
} }
_logger.LogWarning("Message not matched"); // TODO stream.Position = 0;
return; var unhandledBuffer = new byte[stream.Length];
stream.Read(unhandledBuffer, 0, unhandledBuffer.Length);
//if (_messageIdentifierListeners.TryGetValue(result.Identifier.ToLowerInvariant(), out var idListener))
//{ _logger.Log(LogLevel.Warning, $"Socket {SocketId} Message not handled: " + Encoding.UTF8.GetString(unhandledBuffer));
// var userSw = Stopwatch.StartNew(); UnhandledMessage?.Invoke(result);
// await idListener.ProcessAsync(streamMessage).ConfigureAwait(false);
// userSw.Stop();
// userCodeDuration = userSw.Elapsed;
// handledResponse = true;
//}
//else
//{
// foreach (var listener in listeners)
// {
// if (listener.MessageMatches(streamMessage))
// {
// if (listener is PendingRequest pendingRequest)
// {
// lock (_messageListeners)
// _messageListeners.Remove(pendingRequest);
// if (pendingRequest.Completed)
// {
// // Answer to a timed out request, unsub if it is a subscription request
// if (pendingRequest.MessageListener != null)
// {
// _logger.Log(LogLevel.Warning, $"Socket {SocketId} Received subscription info after request timed out; unsubscribing. Consider increasing the RequestTimeout");
// _ = UnsubscribeAsync(pendingRequest.MessageListener).ConfigureAwait(false);
// }
// }
// else
// {
// _logger.Log(LogLevel.Trace, $"Socket {SocketId} - msg {pendingRequest.Id} - received data matched to pending request");
// await pendingRequest.ProcessAsync(streamMessage).ConfigureAwait(false);
// }
// if (!ApiClient.ContinueOnQueryResponse)
// return;
// handledResponse = true;
// break;
// }
// else if (listener is MessageListener subscription)
// {
// currentSubscription = subscription;
// handledResponse = true;
// var userSw = Stopwatch.StartNew();
// await subscription.ProcessAsync(streamMessage).ConfigureAwait(false);
// userSw.Stop();
// userCodeDuration = userSw.Elapsed;
// break;
// }
// }
// }
//}
//if (!handledResponse)
//{
// if (!ApiClient.UnhandledMessageExpected)
// _logger.Log(LogLevel.Warning, $"Socket {SocketId} Message not handled: " + streamMessage.Get(ParsingUtils.GetString));
// UnhandledMessage?.Invoke(streamMessage);
//}
} }
/// <summary> /// <summary>

View File

@ -1,12 +1,7 @@
using CryptoExchange.Net.Converters; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Objects.Sockets;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace CryptoExchange.Net.Sockets namespace CryptoExchange.Net.Sockets
@ -16,8 +11,6 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
public abstract class Subscription public abstract class Subscription
{ {
private bool _outputOriginalData;
/// <summary> /// <summary>
/// Logger /// Logger
/// </summary> /// </summary>
@ -28,18 +21,19 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
public bool Authenticated { get; } public bool Authenticated { get; }
/// <summary>
/// Strings to identify this subscription with
/// </summary>
public abstract List<string> Identifiers { get; } public abstract List<string> Identifiers { get; }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
/// <param name="logger"></param> /// <param name="logger"></param>
/// <param name="apiClient"></param>
/// <param name="authenticated"></param> /// <param name="authenticated"></param>
public Subscription(ILogger logger, ISocketApiClient apiClient, bool authenticated) public Subscription(ILogger logger, bool authenticated)
{ {
_logger = logger; _logger = logger;
_outputOriginalData = apiClient.ApiOptions.OutputOriginalData ?? apiClient.ClientOptions.OutputOriginalData;
Authenticated = authenticated; Authenticated = authenticated;
} }
@ -67,52 +61,11 @@ namespace CryptoExchange.Net.Sockets
/// <returns></returns> /// <returns></returns>
public abstract (bool, CallResult?) MessageMatchesUnsubRequest(ParsedMessage message); public abstract (bool, CallResult?) MessageMatchesUnsubRequest(ParsedMessage message);
/// <summary>
/// Check if the message is an update for this subscription
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
//public abstract bool MessageMatchesEvent(ParsedMessage message);
/// <summary> /// <summary>
/// Handle the update message /// Handle the update message
/// </summary> /// </summary>
/// <param name="message"></param> /// <param name="message"></param>
/// <returns></returns> /// <returns></returns>
public abstract Task HandleEventAsync(DataEvent<ParsedMessage> message); public abstract Task HandleEventAsync(DataEvent<ParsedMessage> message);
///// <summary>
///// Create a data event
///// </summary>
///// <typeparam name="T"></typeparam>
///// <param name="obj"></param>
///// <param name="message"></param>
///// <param name="topic"></param>
///// <param name="type"></param>
///// <returns></returns>
//protected virtual DataEvent<T> CreateDataEvent<T>(T obj, ParsedMessage message, string? topic = null, SocketUpdateType? type = null)
//{
// string? originalData = null;
// if (_outputOriginalData)
// originalData = message.Get(ParsingUtils.GetString);
// return new DataEvent<T>(obj, topic, originalData, message.Timestamp, type);
//}
///// <summary>
///// Deserialize the message to an object using Json.Net
///// </summary>
///// <typeparam name="T"></typeparam>
///// <param name="message"></param>
///// <param name="settings"></param>
///// <returns></returns>
//protected virtual Task<CallResult<T>> DeserializeAsync<T>(StreamMessage message, JsonSerializerSettings settings)
//{
// var serializer = JsonSerializer.Create(settings);
// using var sr = new StreamReader(message.Stream, Encoding.UTF8, false, (int)message.Stream.Length, true);
// using var jsonTextReader = new JsonTextReader(sr);
// var result = serializer.Deserialize<T>(jsonTextReader);
// message.Stream.Position = 0;
// return Task.FromResult(new CallResult<T>(result!));
//}
} }
} }

View File

@ -16,9 +16,8 @@ namespace CryptoExchange.Net.Sockets
/// ctor /// ctor
/// </summary> /// </summary>
/// <param name="logger"></param> /// <param name="logger"></param>
/// <param name="socketApiClient"></param>
/// <param name="authenticated"></param> /// <param name="authenticated"></param>
public SystemSubscription(ILogger logger, ISocketApiClient socketApiClient, bool authenticated = false) : base(logger, socketApiClient, authenticated) public SystemSubscription(ILogger logger, bool authenticated = false) : base(logger, authenticated)
{ {
} }