1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-12-16 11:07:06 +00:00
This commit is contained in:
Jkorf 2025-11-25 16:41:44 +01:00
parent 839ebc4ca9
commit fcef172069
7 changed files with 63 additions and 70 deletions

View File

@ -1,22 +1,22 @@
using CryptoExchange.Net.Converters.MessageParsing; //using CryptoExchange.Net.Converters.MessageParsing;
using CryptoExchange.Net.Converters.MessageParsing.DynamicConverters; //using CryptoExchange.Net.Converters.MessageParsing.DynamicConverters;
using CryptoExchange.Net.Objects; //using CryptoExchange.Net.Objects;
using LightProto; //using LightProto;
using System; //using System;
using System.Collections.Generic; //using System.Collections.Generic;
using System.Net.WebSockets; //using System.Net.WebSockets;
using System.Text; //using System.Text;
namespace CryptoExchange.Net.Protobuf.Converters.Protobuf //namespace CryptoExchange.Net.Protobuf.Converters.Protobuf
{ //{
public abstract class DynamicProtobufConverter<T> : ISocketMessageHandler // public abstract class DynamicProtobufConverter<T> : ISocketMessageHandler
{ // {
public object Deserialize(ReadOnlySpan<byte> data, Type type) // public object Deserialize(ReadOnlySpan<byte> data, Type type)
{ // {
var result = Serializer.Deserialize<T>(data); // var result = Serializer.Deserialize<T>(data);
return result; // return result;
} // }
public abstract string GetMessageIdentifier(ReadOnlySpan<byte> data, WebSocketMessageType? webSocketMessageType); // public abstract string GetMessageIdentifier(ReadOnlySpan<byte> data, WebSocketMessageType? webSocketMessageType);
} // }
} //}

View File

@ -4,11 +4,6 @@
<name>CryptoExchange.Net.Protobuf</name> <name>CryptoExchange.Net.Protobuf</name>
</assembly> </assembly>
<members> <members>
<member name="F:CryptoExchange.Net.Protobuf.Converters.Protobuf.DynamicProtobufConverter._model">
<summary>
Runtime type model
</summary>
</member>
<member name="T:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageAccessor`1"> <member name="T:CryptoExchange.Net.Converters.Protobuf.ProtobufMessageAccessor`1">
<summary> <summary>
System.Text.Json message accessor System.Text.Json message accessor

View File

@ -486,6 +486,10 @@ namespace CryptoExchange.Net.Clients
memoryStream.Position = 0; memoryStream.Position = 0;
originalData = await reader.ReadToEndAsync().ConfigureAwait(false); originalData = await reader.ReadToEndAsync().ConfigureAwait(false);
if (_logger.IsEnabled(LogLevel.Trace))
#warning TODO extension
_logger.LogTrace("[Req {RequestId}] Received response: {Data}", request.RequestId, originalData);
// Continue processing from the memory stream since the response stream is already read and we can't seek it // Continue processing from the memory stream since the response stream is already read and we can't seek it
responseStream.Close(); responseStream.Close();
memoryStream.Position = 0; memoryStream.Position = 0;
@ -494,9 +498,12 @@ namespace CryptoExchange.Net.Clients
if (!response.IsSuccessStatusCode && !requestDefinition.TryParseOnNonSuccess) if (!response.IsSuccessStatusCode && !requestDefinition.TryParseOnNonSuccess)
{ {
// If the response status is not success it is an error by definition
Error error; Error error;
if (response.StatusCode == (HttpStatusCode)418 || response.StatusCode == (HttpStatusCode)429) if (response.StatusCode == (HttpStatusCode)418 || response.StatusCode == (HttpStatusCode)429)
{ {
// Specifically handle rate limit errors
var rateError = await MessageHandler.ParseErrorRateLimitResponse( var rateError = await MessageHandler.ParseErrorRateLimitResponse(
(int)response.StatusCode, (int)response.StatusCode,
state, state,
@ -512,6 +519,7 @@ namespace CryptoExchange.Net.Clients
} }
else else
{ {
// Handle a 'normal' error response. Can still be either a json error message or some random HTML or other string
error = await MessageHandler.ParseErrorResponse( error = await MessageHandler.ParseErrorResponse(
(int)response.StatusCode, (int)response.StatusCode,
state, state,
@ -526,7 +534,7 @@ namespace CryptoExchange.Net.Clients
// Success status code and expected empty response, assume it's correct // Success status code and expected empty response, assume it's correct
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, 0, originalData, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, null); return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, 0, originalData, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, null);
// Data response received // Data response received, inspect the message and check if it is an error or not
var parsedError = await MessageHandler.CheckForErrorResponse( var parsedError = await MessageHandler.CheckForErrorResponse(
requestDefinition, requestDefinition,
state, state,
@ -547,8 +555,17 @@ namespace CryptoExchange.Net.Clients
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, originalData, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, parsedError); return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, originalData, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, parsedError);
} }
// Try deserialization into the expected type
var (deserializeResult, deserializeError) = await MessageHandler.TryDeserializeAsync<T>(responseStream, state, cancellationToken).ConfigureAwait(false); var (deserializeResult, deserializeError) = await MessageHandler.TryDeserializeAsync<T>(responseStream, state, cancellationToken).ConfigureAwait(false);
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, originalData, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, deserializeResult, deserializeError); if (deserializeError != null)
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, originalData, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, deserializeResult, deserializeError); ;
// Check the deserialized response to see if it's an error or not
var responseError = MessageHandler.CheckDeserializedResponse(response.ResponseHeaders, deserializeResult);
if (responseError != null)
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, originalData, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, deserializeResult, responseError);
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, originalData, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, deserializeResult, null);
} }
catch (HttpRequestException requestException) catch (HttpRequestException requestException)
{ {

View File

@ -44,7 +44,10 @@ namespace CryptoExchange.Net.Converters.MessageParsing.DynamicConverters
Stream responseStream); Stream responseStream);
/// <summary> /// <summary>
/// Check if the response is an error response; if so return the error /// Check if the response is an error response; if so return the error.<br />
/// Note that if the API returns a standard result wrapper, something like this:
/// <code>{ "code": 400, "msg": "error", "data": {} }</code>
/// then the `CheckDeserializedResponse` method should be used for checking the result
/// </summary> /// </summary>
ValueTask<Error?> CheckForErrorResponse( ValueTask<Error?> CheckForErrorResponse(
RequestDefinition request, RequestDefinition request,
@ -59,6 +62,11 @@ namespace CryptoExchange.Net.Converters.MessageParsing.DynamicConverters
Stream responseStream, Stream responseStream,
object? state, object? state,
CancellationToken ct); CancellationToken ct);
/// <summary>
/// Check whether the resulting T object indicates an error or not
/// </summary>
Error? CheckDeserializedResponse<T>(HttpResponseHeaders responseHeaders, T result);
} }
} }

View File

@ -119,49 +119,18 @@ namespace CryptoExchange.Net.Converters.SystemTextJson.MessageConverters
} }
return (result, null); return (result, null);
} }
catch (HttpRequestException requestException) catch (JsonException ex)
{ {
// Request exception, can't reach server for instance var info = $"Json deserialization failed: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
var error = new WebError(requestException.Message, requestException); return (default, new DeserializeError(info, ex));
return (default, error);
} }
catch (OperationCanceledException canceledException) catch (Exception ex)
{ {
if (cancellationToken != default && canceledException.CancellationToken == cancellationToken) return (default, new DeserializeError($"Json deserialization failed: {ex.Message}", ex));
{
// Cancellation token canceled by caller
return (default, new CancellationRequestedError(canceledException));
} }
else
{
// Request timed out
var error = new WebError($"Request timed out", exception: canceledException);
error.ErrorType = ErrorType.Timeout;
return (default, error);
}
}
catch (ArgumentException argumentException)
{
if (argumentException.Message.StartsWith("Only HTTP/"))
{
// Unsupported HTTP version error .net framework
var error = ArgumentError.Invalid(nameof(RestExchangeOptions.HttpVersion), $"Invalid HTTP version: " + argumentException.Message);
return (default, error);
} }
throw; /// <inheritdoc />
} public virtual Error? CheckDeserializedResponse<T>(HttpResponseHeaders responseHeaders, T result) => null;
catch (NotSupportedException notSupportedException)
{
if (notSupportedException.Message.StartsWith("Request version value must be one of"))
{
// Unsupported HTTP version error dotnet code
var error = ArgumentError.Invalid(nameof(RestExchangeOptions.HttpVersion), $"Invalid HTTP version: " + notSupportedException.Message);
return (default, error);
}
throw;
}
}
} }
} }

View File

@ -80,11 +80,15 @@ namespace CryptoExchange.Net.Objects
/// <summary> /// <summary>
/// Get the parameter collection based on the ParameterPosition /// Get the parameter collection based on the ParameterPosition
/// </summary> /// </summary>
public IDictionary<string, object>? GetPositionParameters() public IDictionary<string, object> GetPositionParameters()
{ {
if (ParameterPosition == HttpMethodParameterPosition.InBody) if (ParameterPosition == HttpMethodParameterPosition.InBody)
{
BodyParameters ??= new Dictionary<string, object>();
return BodyParameters; return BodyParameters;
}
QueryParameters ??= new Dictionary<string, object>();
return QueryParameters; return QueryParameters;
} }

View File

@ -565,7 +565,7 @@ namespace CryptoExchange.Net.Sockets
if (deserializationType == null) if (deserializationType == null)
{ {
// No handler found for identifier either, can't process // No handler found for identifier either, can't process
_logger.LogWarning("Failed to determine message type. Data: {Message}", Encoding.UTF8.GetString(data.ToArray())); _logger.LogWarning("Failed to determine message type for identifier {Identifier}. Data: {Message}", messageIdentifier, Encoding.UTF8.GetString(data.ToArray()));
return; return;
} }