1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-09-05 23:21:56 +00:00

Compare commits

..

No commits in common. "master" and "CryptoExchange.Net.Protobuf.9.5.0" have entirely different histories.

33 changed files with 141 additions and 364 deletions

View File

@ -6,9 +6,9 @@
<PackageId>CryptoExchange.Net.Protobuf</PackageId>
<Authors>JKorf</Authors>
<Description>Protobuf support for CryptoExchange.Net</Description>
<PackageVersion>9.7.0</PackageVersion>
<AssemblyVersion>9.7.0</AssemblyVersion>
<FileVersion>9.7.0</FileVersion>
<PackageVersion>9.5.0</PackageVersion>
<AssemblyVersion>9.5.0</AssemblyVersion>
<FileVersion>9.5.0</FileVersion>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageTags>CryptoExchange;CryptoExchange.Net</PackageTags>
<RepositoryType>git</RepositoryType>
@ -41,7 +41,7 @@
<DocumentationFile>CryptoExchange.Net.Protobuf.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CryptoExchange.Net" Version="9.7.0" />
<PackageReference Include="CryptoExchange.Net" Version="9.5.0" />
<PackageReference Include="protobuf-net" Version="3.2.56" />
</ItemGroup>
</Project>

View File

@ -5,12 +5,6 @@
Protobuf support for CryptoExchange.Net.
## Release notes
* Version 9.7.0 - 01 Sep 2025
* Updated CryptoExchange.Net version to 9.7.0, see https://github.com/JKorf/CryptoExchange.Net/releases/
* Version 9.6.0 - 25 Aug 2025
* Updated CryptoExchange.Net version to 9.6.0
* Version 9.5.0 - 19 Aug 2025
* Updated CryptoExchange.Net version to 9.5.0

View File

@ -5,7 +5,6 @@ using NUnit.Framework.Legacy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
@ -114,7 +113,6 @@ namespace CryptoExchange.Net.UnitTests
{
var result = new WebCallResult<TestObjectResult>(
System.Net.HttpStatusCode.OK,
HttpVersion.Version11,
new KeyValuePair<string, string[]>[0],
TimeSpan.FromSeconds(1),
null,
@ -145,7 +143,6 @@ namespace CryptoExchange.Net.UnitTests
{
var result = new WebCallResult<TestObjectResult>(
System.Net.HttpStatusCode.OK,
HttpVersion.Version11,
new KeyValuePair<string, string[]>[0],
TimeSpan.FromSeconds(1),
null,

View File

@ -28,7 +28,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations.Sockets
return new CallResult(null);
}
protected override Query GetSubQuery(SocketConnection connection) => new TestQuery("sub", new object(), false, 1);
protected override Query GetUnsubQuery(SocketConnection connection) => new TestQuery("unsub", new object(), false, 1);
public override Query GetSubQuery(SocketConnection connection) => new TestQuery("sub", new object(), false, 1);
public override Query GetUnsubQuery() => new TestQuery("unsub", new object(), false, 1);
}
}

View File

@ -28,7 +28,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations.Sockets
return new CallResult(null);
}
protected override Query GetSubQuery(SocketConnection connection) => new TestChannelQuery(_channel, "subscribe", false, 1);
protected override Query GetUnsubQuery(SocketConnection connection) => new TestChannelQuery(_channel, "unsubscribe", false, 1);
public override Query GetSubQuery(SocketConnection connection) => new TestChannelQuery(_channel, "subscribe", false, 1);
public override Query GetUnsubQuery() => new TestChannelQuery(_channel, "unsubscribe", false, 1);
}
}

View File

@ -60,8 +60,8 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
request.Setup(c => c.GetHeaders()).Returns(() => headers.ToArray());
var factory = Mock.Get(Api1.RequestFactory);
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Callback<Version, HttpMethod, Uri, int>((version, method, uri, id) =>
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Callback<HttpMethod, Uri, int>((method, uri, id) =>
{
request.Setup(a => a.Uri).Returns(uri);
request.Setup(a => a.Method).Returns(method);
@ -69,8 +69,8 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
.Returns(request.Object);
factory = Mock.Get(Api2.RequestFactory);
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Callback<Version, HttpMethod, Uri, int>((version, method, uri, id) =>
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Callback<HttpMethod, Uri, int>((method, uri, id) =>
{
request.Setup(a => a.Uri).Returns(uri);
request.Setup(a => a.Method).Returns(method);
@ -90,12 +90,12 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Throws(we);
var factory = Mock.Get(Api1.RequestFactory);
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Returns(request.Object);
factory = Mock.Get(Api2.RequestFactory);
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Returns(request.Object);
}
@ -118,13 +118,13 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
request.Setup(c => c.GetHeaders()).Returns(headers.ToArray());
var factory = Mock.Get(Api1.RequestFactory);
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Callback<Version, HttpMethod, Uri, int>((version, method, uri, id) => request.Setup(a => a.Uri).Returns(uri))
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Callback<HttpMethod, Uri, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(uri))
.Returns(request.Object);
factory = Mock.Get(Api2.RequestFactory);
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Callback<Version, HttpMethod, Uri, int>((version, method, uri, id) => request.Setup(a => a.Uri).Returns(uri))
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Callback<HttpMethod, Uri, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(uri))
.Returns(request.Object);
}
}

View File

@ -106,7 +106,7 @@ namespace CryptoExchange.Net.Clients
options,
apiOptions)
{
RequestFactory.Configure(options, httpClient);
RequestFactory.Configure(options.Proxy, options.RequestTimeout, httpClient);
}
/// <summary>
@ -239,7 +239,7 @@ namespace CryptoExchange.Net.Clients
additionalHeaders);
_logger.RestApiSendRequest(request.RequestId, definition, request.Content, string.IsNullOrEmpty(request.Uri.Query) ? "-" : request.Uri.Query, string.Join(", ", request.GetHeaders().Select(h => h.Key + $"=[{string.Join(",", h.Value)}]")));
TotalRequestsMade++;
var result = await GetResponseAsync<T>(definition, request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false);
var result = await GetResponseAsync<T>(request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false);
if (result.Error is not CancellationRequestedError)
{
var originalData = OutputOriginalData ? result.OriginalData : "[Data only available when OutputOriginal = true]";
@ -388,7 +388,7 @@ namespace CryptoExchange.Net.Clients
queryString = $"?{queryString}";
var uri = new Uri(baseAddress.AppendPath(definition.Path) + queryString);
var request = RequestFactory.Create(ClientOptions.HttpVersion, definition.Method, uri, requestId);
var request = RequestFactory.Create(definition.Method, uri, requestId);
request.Accept = Constants.JsonContentHeader;
foreach (var header in requestConfiguration.Headers)
@ -424,13 +424,11 @@ namespace CryptoExchange.Net.Clients
/// <summary>
/// Executes the request and returns the result deserialized into the type parameter class
/// </summary>
/// <param name="requestDefinition">The request definition</param>
/// <param name="request">The request object to execute</param>
/// <param name="gate">The ratelimit gate used</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns></returns>
protected virtual async Task<WebCallResult<T>> GetResponseAsync<T>(
RequestDefinition requestDefinition,
IRequest request,
IRateLimitGate? gate,
CancellationToken cancellationToken)
@ -443,11 +441,14 @@ namespace CryptoExchange.Net.Clients
{
response = await request.GetResponseAsync(cancellationToken).ConfigureAwait(false);
sw.Stop();
var statusCode = response.StatusCode;
var headers = response.ResponseHeaders;
var responseLength = response.ContentLength;
responseStream = await response.GetResponseStreamAsync().ConfigureAwait(false);
var outputOriginalData = ApiOptions.OutputOriginalData ?? ClientOptions.OutputOriginalData;
accessor = CreateAccessor();
if (!response.IsSuccessStatusCode && !requestDefinition.TryParseOnNonSuccess)
if (!response.IsSuccessStatusCode)
{
// Error response
var readResult = await accessor.Read(responseStream, true).ConfigureAwait(false);
@ -469,25 +470,27 @@ namespace CryptoExchange.Net.Clients
error = ParseErrorResponse((int)response.StatusCode, response.ResponseHeaders, accessor, readResult.Error?.Exception);
}
#pragma warning disable CS0618 // Type or member is obsolete
if (error.Code == null || error.Code == 0)
error.Code = (int)response.StatusCode;
#pragma warning restore CS0618 // Type or member is obsolete
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error!);
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error!);
}
var valid = await accessor.Read(responseStream, outputOriginalData).ConfigureAwait(false);
if (typeof(T) == typeof(object))
// Success status code and expected empty response, assume it's correct
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, 0, accessor.OriginalDataAvailable ? accessor.GetOriginalString() : "[Data only available when OutputOriginal = true in client options]", request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, null);
return new WebCallResult<T>(statusCode, headers, sw.Elapsed, 0, accessor.OriginalDataAvailable ? accessor.GetOriginalString() : "[Data only available when OutputOriginal = true in client options]", request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, null);
if (!valid)
{
// Invalid json
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, valid.Error);
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, valid.Error);
}
// Json response received
var parsedError = TryParseError(requestDefinition, response.ResponseHeaders, accessor);
var parsedError = TryParseError(response.ResponseHeaders, accessor);
if (parsedError != null)
{
if (parsedError is ServerRateLimitError rateError)
@ -500,55 +503,33 @@ namespace CryptoExchange.Net.Clients
}
// Success status code, but TryParseError determined it was an error response
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, parsedError);
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, parsedError);
}
var deserializeResult = accessor.Deserialize<T>();
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, deserializeResult.Data, deserializeResult.Error);
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, deserializeResult.Data, deserializeResult.Error);
}
catch (HttpRequestException requestException)
{
// Request exception, can't reach server for instance
var error = new WebError(requestException.Message, requestException);
return new WebCallResult<T>(null, null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
}
catch (OperationCanceledException canceledException)
{
if (cancellationToken != default && canceledException.CancellationToken == cancellationToken)
{
// Cancellation token canceled by caller
return new WebCallResult<T>(null, null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, new CancellationRequestedError(canceledException));
return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, new CancellationRequestedError(canceledException));
}
else
{
// Request timed out
var error = new WebError($"Request timed out", exception: canceledException);
error.ErrorType = ErrorType.Timeout;
return new WebCallResult<T>(null, null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, 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 {request.HttpVersion}: " + argumentException.Message);
return new WebCallResult<T>(null, null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
}
throw;
}
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 {request.HttpVersion}: " + notSupportedException.Message);
return new WebCallResult<T>(null, null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
}
throw;
}
finally
{
accessor?.Clear();
@ -562,11 +543,10 @@ namespace CryptoExchange.Net.Clients
/// This method will be called for each response to be able to check if the response is an error or not.
/// If the response is an error this method should return the parsed error, else it should return null
/// </summary>
/// <param name="requestDefinition">Request definition</param>
/// <param name="accessor">Data accessor</param>
/// <param name="responseHeaders">The response headers</param>
/// <returns>Null if not an error, Error otherwise</returns>
protected virtual Error? TryParseError(RequestDefinition requestDefinition, KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor) => null;
protected virtual Error? TryParseError(KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor) => null;
/// <summary>
/// Can be used to indicate that a request should be retried. Defaults to false. Make sure to retry a max number of times (based on the the tries parameter) or the request will retry forever.
@ -693,21 +673,21 @@ namespace CryptoExchange.Net.Clients
{
base.SetOptions(options);
RequestFactory.UpdateSettings(options.Proxy, options.RequestTimeout ?? ClientOptions.RequestTimeout, ClientOptions.HttpKeepAliveInterval);
RequestFactory.UpdateSettings(options.Proxy, options.RequestTimeout ?? ClientOptions.RequestTimeout);
}
internal async Task<WebCallResult<bool>> SyncTimeAsync()
{
var timeSyncParams = GetTimeSyncInfo();
if (timeSyncParams == null)
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, true, null);
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, true, null);
if (await timeSyncParams.TimeSyncState.Semaphore.WaitAsync(0).ConfigureAwait(false))
{
if (!timeSyncParams.SyncTime || DateTime.UtcNow - timeSyncParams.TimeSyncState.LastSyncTime < timeSyncParams.RecalculationInterval)
{
timeSyncParams.TimeSyncState.Semaphore.Release();
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, true, null);
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, true, null);
}
var localTime = DateTime.UtcNow;
@ -736,7 +716,7 @@ namespace CryptoExchange.Net.Clients
timeSyncParams.TimeSyncState.Semaphore.Release();
}
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, true, null);
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, true, null);
}
private bool ShouldCache(RequestDefinition definition)

View File

@ -270,7 +270,7 @@ namespace CryptoExchange.Net.Clients
}
var waitEvent = new AsyncResetEvent(false);
var subQuery = subscription.CreateSubscriptionQuery(socketConnection);
var subQuery = subscription.GetSubQuery(socketConnection);
if (subQuery != null)
{
// Send the request and wait for answer

View File

@ -6,9 +6,9 @@
<PackageId>CryptoExchange.Net</PackageId>
<Authors>JKorf</Authors>
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
<PackageVersion>9.7.0</PackageVersion>
<AssemblyVersion>9.7.0</AssemblyVersion>
<FileVersion>9.7.0</FileVersion>
<PackageVersion>9.5.0</PackageVersion>
<AssemblyVersion>9.5.0</AssemblyVersion>
<FileVersion>9.5.0</FileVersion>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange;CryptoExchange.Net</PackageTags>
<RepositoryType>git</RepositoryType>

View File

@ -28,10 +28,6 @@ namespace CryptoExchange.Net.Interfaces
/// </summary>
Uri Uri { get; }
/// <summary>
/// HTTP protocol version
/// </summary>
Version HttpVersion { get; }
/// <summary>
/// internal request id for tracing
/// </summary>
int RequestId { get; }

View File

@ -1,5 +1,4 @@
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Options;
using System;
using System.Net.Http;
@ -13,21 +12,25 @@ namespace CryptoExchange.Net.Interfaces
/// <summary>
/// Create a request for an uri
/// </summary>
IRequest Create(Version httpRequestVersion, HttpMethod method, Uri uri, int requestId);
/// <param name="method"></param>
/// <param name="uri"></param>
/// <param name="requestId"></param>
/// <returns></returns>
IRequest Create(HttpMethod method, Uri uri, int requestId);
/// <summary>
/// Configure the requests created by this factory
/// </summary>
/// <param name="options">Rest client options</param>
/// <param name="requestTimeout">Request timeout to use</param>
/// <param name="httpClient">Optional shared http client instance</param>
void Configure(RestExchangeOptions options, HttpClient? httpClient = null);
/// <param name="proxy">Optional proxy to use when no http client is provided</param>
void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient = null);
/// <summary>
/// Update settings
/// </summary>
/// <param name="proxy">Proxy to use</param>
/// <param name="requestTimeout">Request timeout to use</param>
/// <param name="httpKeepAliveInterval">Http client keep alive interval</param>
void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout, TimeSpan? httpKeepAliveInterval);
void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout);
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
@ -16,11 +15,6 @@ namespace CryptoExchange.Net.Interfaces
/// </summary>
HttpStatusCode StatusCode { get; }
/// <summary>
/// Http protocol version
/// </summary>
Version HttpVersion { get; }
/// <summary>
/// Whether the status code indicates a success status
/// </summary>

View File

@ -1,8 +1,5 @@
using CryptoExchange.Net.Objects;
using System;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
namespace CryptoExchange.Net
@ -46,58 +43,5 @@ namespace CryptoExchange.Net
return clientOrderId;
}
/// <summary>
/// Create a new HttpMessageHandler instance
/// </summary>
public static HttpMessageHandler CreateHttpClientMessageHandler(ApiProxy? proxy, TimeSpan? keepAliveInterval)
{
#if NET5_0_OR_GREATER
var socketHandler = new SocketsHttpHandler();
try
{
if (keepAliveInterval != null && keepAliveInterval != TimeSpan.Zero)
{
socketHandler.KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always;
socketHandler.KeepAlivePingDelay = keepAliveInterval.Value;
socketHandler.KeepAlivePingTimeout = TimeSpan.FromSeconds(10);
}
socketHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
socketHandler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
}
catch (PlatformNotSupportedException) { }
catch (NotImplementedException) { } // Mono runtime throws NotImplementedException
if (proxy != null)
{
socketHandler.Proxy = new WebProxy
{
Address = new Uri($"{proxy.Host}:{proxy.Port}"),
Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
};
}
return socketHandler;
#else
var httpHandler = new HttpClientHandler();
try
{
httpHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
httpHandler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
}
catch (PlatformNotSupportedException) { }
catch (NotImplementedException) { } // Mono runtime throws NotImplementedException
if (proxy != null)
{
httpHandler.Proxy = new WebProxy
{
Address = new Uri($"{proxy.Host}:{proxy.Port}"),
Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
};
}
return httpHandler;
#endif
}
}
}

View File

@ -205,11 +205,6 @@ namespace CryptoExchange.Net.Objects
/// The request http method
/// </summary>
public HttpMethod? RequestMethod { get; set; }
/// <summary>
/// HTTP protocol version
/// </summary>
public Version? HttpVersion { get; set; }
/// <summary>
/// The headers sent with the request
@ -256,7 +251,6 @@ namespace CryptoExchange.Net.Objects
/// </summary>
public WebCallResult(
HttpStatusCode? code,
Version? httpVersion,
KeyValuePair<string, string[]>[]? responseHeaders,
TimeSpan? responseTime,
string? originalData,
@ -268,7 +262,6 @@ namespace CryptoExchange.Net.Objects
Error? error) : base(error)
{
ResponseStatusCode = code;
HttpVersion = httpVersion;
ResponseHeaders = responseHeaders;
ResponseTime = responseTime;
RequestId = requestId;
@ -293,7 +286,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns>
public WebCallResult AsError(Error error)
{
return new WebCallResult(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error);
return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error);
}
/// <summary>
@ -304,7 +297,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns>
public WebCallResult<K> As<K>([AllowNull] K data)
{
return new WebCallResult<K>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, 0, null, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Server, data, Error);
return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, 0, null, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Server, data, Error);
}
/// <summary>
@ -341,7 +334,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns>
public WebCallResult<K> AsError<K>(Error error)
{
return new WebCallResult<K>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, 0, null, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Server, default, error);
return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, 0, null, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Server, default, error);
}
/// <inheritdoc />
@ -362,11 +355,6 @@ namespace CryptoExchange.Net.Objects
/// </summary>
public HttpMethod? RequestMethod { get; set; }
/// <summary>
/// HTTP protocol version
/// </summary>
public Version? HttpVersion { get; set; }
/// <summary>
/// The headers sent with the request
/// </summary>
@ -415,9 +403,21 @@ namespace CryptoExchange.Net.Objects
/// <summary>
/// Create a new result
/// </summary>
/// <param name="code"></param>
/// <param name="responseHeaders"></param>
/// <param name="responseTime"></param>
/// <param name="responseLength"></param>
/// <param name="originalData"></param>
/// <param name="requestId"></param>
/// <param name="requestUrl"></param>
/// <param name="requestBody"></param>
/// <param name="requestMethod"></param>
/// <param name="requestHeaders"></param>
/// <param name="dataSource"></param>
/// <param name="data"></param>
/// <param name="error"></param>
public WebCallResult(
HttpStatusCode? code,
Version? httpVersion,
KeyValuePair<string, string[]>[]? responseHeaders,
TimeSpan? responseTime,
long? responseLength,
@ -431,7 +431,6 @@ namespace CryptoExchange.Net.Objects
[AllowNull] T data,
Error? error) : base(data, originalData, error)
{
HttpVersion = httpVersion;
ResponseStatusCode = code;
ResponseHeaders = responseHeaders;
ResponseTime = responseTime;
@ -451,7 +450,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns>
public new WebCallResult AsDataless()
{
return new WebCallResult(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, Error);
return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, Error);
}
/// <summary>
/// Copy as a dataless result
@ -459,14 +458,14 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns>
public new WebCallResult AsDatalessError(Error error)
{
return new WebCallResult(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error);
return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error);
}
/// <summary>
/// Create a new error result
/// </summary>
/// <param name="error">The error</param>
public WebCallResult(Error? error) : this(null, null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, default, error) { }
public WebCallResult(Error? error) : this(null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, default, error) { }
/// <summary>
/// Copy the WebCallResult to a new data type
@ -476,7 +475,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns>
public new WebCallResult<K> As<K>([AllowNull] K data)
{
return new WebCallResult<K>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, Error);
return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, Error);
}
/// <summary>
@ -487,7 +486,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns>
public new WebCallResult<K> AsError<K>(Error error)
{
return new WebCallResult<K>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, default, error);
return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, default, error);
}
/// <summary>
@ -499,7 +498,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns>
public new WebCallResult<K> AsErrorWithData<K>(Error error, K data)
{
return new WebCallResult<K>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, error);
return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, error);
}
/// <summary>
@ -570,7 +569,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns>
internal WebCallResult<T> Cached()
{
return new WebCallResult<T>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Cache, Data, Error);
return new WebCallResult<T>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Cache, Data, Error);
}
/// <inheritdoc />

View File

@ -251,20 +251,4 @@ namespace CryptoExchange.Net.Objects
/// </summary>
DEX
}
/// <summary>
/// Timeout behavior for queries
/// </summary>
public enum TimeoutBehavior
{
/// <summary>
/// Fail the request
/// </summary>
Fail,
/// <summary>
/// Mark the query as successful
/// </summary>
Succeed
}
}

View File

@ -11,11 +11,9 @@ namespace CryptoExchange.Net.Objects
private int? _code;
/// <summary>
/// The int error code the server returned; or the http status code int value if there was no error code.<br />
/// <br />
/// <i>Note:</i><br />
/// The <see cref="ErrorCode"/> property should be used for more generic error checking; it might contain a string error code if the server does not return an int code.
/// The error code from the server
/// </summary>
[Obsolete("Use ErrorCode instead", false)]
public int? Code
{
get

View File

@ -1,7 +1,5 @@
using CryptoExchange.Net.Authentication;
using System;
using System.Net;
using System.Net.Http;
namespace CryptoExchange.Net.Objects.Options
{
@ -30,20 +28,6 @@ namespace CryptoExchange.Net.Objects.Options
/// </summary>
public TimeSpan CachingMaxAge { get; set; } = TimeSpan.FromSeconds(5);
/// <summary>
/// The HTTP protocol version to use, typically 2.0 or 1.1
/// </summary>
public Version HttpVersion { get; set; }
#if NET5_0_OR_GREATER
= new Version(2, 0);
#else
= new Version(1, 1);
#endif
/// <summary>
/// Http client keep alive interval for keeping connections open
/// </summary>
public TimeSpan? HttpKeepAliveInterval { get; set; } = TimeSpan.FromSeconds(15);
/// <summary>
/// Set the values of this options on the target options
/// </summary>
@ -59,8 +43,6 @@ namespace CryptoExchange.Net.Objects.Options
item.RateLimitingBehaviour = RateLimitingBehaviour;
item.CachingEnabled = CachingEnabled;
item.CachingMaxAge = CachingMaxAge;
item.HttpVersion = HttpVersion;
item.HttpKeepAliveInterval = HttpKeepAliveInterval;
return item;
}
}

View File

@ -62,11 +62,6 @@ namespace CryptoExchange.Net.Objects
/// </summary>
public bool PreventCaching { get; set; }
/// <summary>
/// Whether the response to this requests should attempted to be parsed even when the status indicates failure
/// </summary>
public bool TryParseOnNonSuccess { get; set; }
/// <summary>
/// Connection id
/// </summary>

View File

@ -46,7 +46,6 @@ namespace CryptoExchange.Net.Objects
/// <param name="parameterPosition">Parameter position</param>
/// <param name="arraySerialization">Array serialization type</param>
/// <param name="preventCaching">Prevent request caching</param>
/// <param name="tryParseOnNonSuccess">Try parse the response even when status is not success</param>
/// <returns></returns>
public RequestDefinition GetOrCreate(
HttpMethod method,
@ -58,9 +57,8 @@ namespace CryptoExchange.Net.Objects
RequestBodyFormat? requestBodyFormat = null,
HttpMethodParameterPosition? parameterPosition = null,
ArrayParametersSerialization? arraySerialization = null,
bool? preventCaching = null,
bool? tryParseOnNonSuccess = null)
=> GetOrCreate(method + path, method, path, rateLimitGate, weight, authenticated, limitGuard, requestBodyFormat, parameterPosition, arraySerialization, preventCaching, tryParseOnNonSuccess);
bool? preventCaching = null)
=> GetOrCreate(method + path, method, path, rateLimitGate, weight, authenticated, limitGuard, requestBodyFormat, parameterPosition, arraySerialization, preventCaching);
/// <summary>
/// Get a definition if it is already in the cache or create a new definition and add it to the cache
@ -76,7 +74,6 @@ namespace CryptoExchange.Net.Objects
/// <param name="parameterPosition">Parameter position</param>
/// <param name="arraySerialization">Array serialization type</param>
/// <param name="preventCaching">Prevent request caching</param>
/// <param name="tryParseOnNonSuccess">Try parse the response even when status is not success</param>
/// <returns></returns>
public RequestDefinition GetOrCreate(
string identifier,
@ -89,8 +86,7 @@ namespace CryptoExchange.Net.Objects
RequestBodyFormat? requestBodyFormat = null,
HttpMethodParameterPosition? parameterPosition = null,
ArrayParametersSerialization? arraySerialization = null,
bool? preventCaching = null,
bool? tryParseOnNonSuccess = null)
bool? preventCaching = null)
{
if (!_definitions.TryGetValue(identifier, out var def))
@ -104,8 +100,7 @@ namespace CryptoExchange.Net.Objects
ArraySerialization = arraySerialization,
RequestBodyFormat = requestBodyFormat,
ParameterPosition = parameterPosition,
PreventCaching = preventCaching ?? false,
TryParseOnNonSuccess = tryParseOnNonSuccess ?? false
PreventCaching = preventCaching ?? false
};
_definitions.TryAdd(identifier, def);
}

View File

@ -50,9 +50,6 @@ namespace CryptoExchange.Net.Requests
/// <inheritdoc />
public Uri Uri => _request.RequestUri!;
/// <inheritdoc />
public Version HttpVersion => _request.Version!;
/// <inheritdoc />
public int RequestId { get; }
@ -84,9 +81,7 @@ namespace CryptoExchange.Net.Requests
/// <inheritdoc />
public async Task<IResponse> GetResponseAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.SendAsync(_request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
return new Response(response);
return new Response(await _httpClient.SendAsync(_request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false));
}
}
}

View File

@ -3,7 +3,6 @@ using System.Net;
using System.Net.Http;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Options;
namespace CryptoExchange.Net.Requests
{
@ -15,43 +14,54 @@ namespace CryptoExchange.Net.Requests
private HttpClient? _httpClient;
/// <inheritdoc />
public void Configure(RestExchangeOptions options, HttpClient? client = null)
public void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? client = null)
{
if (client == null)
client = CreateClient(options.Proxy, options.RequestTimeout, options.HttpKeepAliveInterval);
client = CreateClient(proxy, requestTimeout);
_httpClient = client;
}
/// <inheritdoc />
public IRequest Create(Version httpRequestVersion, HttpMethod method, Uri uri, int requestId)
public IRequest Create(HttpMethod method, Uri uri, int requestId)
{
if (_httpClient == null)
throw new InvalidOperationException("Cant create request before configuring http client");
var requestMessage = new HttpRequestMessage(method, uri);
requestMessage.Version = httpRequestVersion;
#if NET5_0_OR_GREATER
requestMessage.VersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
#endif
return new Request(requestMessage, _httpClient, requestId);
return new Request(new HttpRequestMessage(method, uri), _httpClient, requestId);
}
/// <inheritdoc />
public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout, TimeSpan? httpKeepAliveInterval)
public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout)
{
_httpClient = CreateClient(proxy, requestTimeout, httpKeepAliveInterval);
_httpClient = CreateClient(proxy, requestTimeout);
}
private static HttpClient CreateClient(ApiProxy? proxy, TimeSpan requestTimeout, TimeSpan? httpKeepAliveInterval)
private static HttpClient CreateClient(ApiProxy? proxy, TimeSpan requestTimeout)
{
var handler = LibraryHelpers.CreateHttpClientMessageHandler(proxy, httpKeepAliveInterval);
var handler = new HttpClientHandler();
try
{
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
}
catch (PlatformNotSupportedException) { }
catch (NotImplementedException) { } // Mono runtime throws NotImplementedException
if (proxy != null)
{
handler.Proxy = new WebProxy
{
Address = new Uri($"{proxy.Host}:{proxy.Port}"),
Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
};
}
var client = new HttpClient(handler)
{
Timeout = requestTimeout
Timeout = requestTimeout
};
return client;
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
@ -19,9 +18,6 @@ namespace CryptoExchange.Net.Requests
/// <inheritdoc />
public HttpStatusCode StatusCode => _response.StatusCode;
/// <inheritdoc />
public Version HttpVersion => _response.Version;
/// <inheritdoc />
public bool IsSuccessStatusCode => _response.IsSuccessStatusCode;

View File

@ -48,7 +48,6 @@ namespace CryptoExchange.Net.SharedApis
WebCallResult<T> result,
INextPageToken? nextPageToken = null) :
base(result.ResponseStatusCode,
result.HttpVersion,
result.ResponseHeaders,
result.ResponseTime,
result.ResponseLength,
@ -76,7 +75,6 @@ namespace CryptoExchange.Net.SharedApis
WebCallResult<T> result,
INextPageToken? nextPageToken = null) :
base(result.ResponseStatusCode,
result.HttpVersion,
result.ResponseHeaders,
result.ResponseTime,
result.ResponseLength,
@ -102,7 +100,6 @@ namespace CryptoExchange.Net.SharedApis
string exchange,
TradingMode[]? dataTradeModes,
HttpStatusCode? code,
Version? httpVersion,
KeyValuePair<string, string[]>[]? responseHeaders,
TimeSpan? responseTime,
long? responseLength,
@ -117,7 +114,6 @@ namespace CryptoExchange.Net.SharedApis
Error? error,
INextPageToken? nextPageToken = null) : base(
code,
httpVersion,
responseHeaders,
responseTime,
responseLength,
@ -144,7 +140,7 @@ namespace CryptoExchange.Net.SharedApis
/// <returns></returns>
public new ExchangeWebResult<K> As<K>([AllowNull] K data)
{
return new ExchangeWebResult<K>(Exchange, DataTradeMode, ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, Error, NextPageToken);
return new ExchangeWebResult<K>(Exchange, DataTradeMode, ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, Error, NextPageToken);
}
/// <inheritdoc />

View File

@ -1,7 +1,6 @@
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Logging.Extensions;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.Objects.Sockets;
using CryptoExchange.Net.RateLimiting;
using Microsoft.Extensions.Logging;
@ -253,11 +252,6 @@ namespace CryptoExchange.Net.Sockets
await (OnConnectRateLimited?.Invoke() ?? Task.CompletedTask).ConfigureAwait(false);
return new CallResult(new ServerRateLimitError(we.Message, we));
}
if (_socket.HttpStatusCode == HttpStatusCode.Unauthorized)
{
return new CallResult(new ServerError(new ErrorInfo(ErrorType.Unauthorized, "Server returned status code `401` when `101` was expected")));
}
#else
// ClientWebSocket.HttpStatusCode is only available in .NET6+ https://learn.microsoft.com/en-us/dotnet/api/system.net.websockets.clientwebsocket.httpstatuscode?view=net-8.0
// Try to read 429 from the message instead

View File

@ -29,11 +29,6 @@ namespace CryptoExchange.Net.Sockets
/// </summary>
public TimeSpan? RequestTimeout { get; set; }
/// <summary>
/// What should happen if the query times out
/// </summary>
public TimeoutBehavior TimeoutBehavior { get; set; } = TimeoutBehavior.Fail;
/// <summary>
/// The number of required responses. Can be more than 1 when for example subscribing multiple symbols streams in a single request,
/// and each symbol receives it's own confirmation response
@ -188,7 +183,7 @@ namespace CryptoExchange.Net.Sockets
/// <inheritdoc />
public override async Task<CallResult> Handle(SocketConnection connection, DataEvent<object> message, MessageHandlerLink check)
{
if (!PreCheckMessage(connection, message))
if (!PreCheckMessage(message))
return CallResult.SuccessResult;
CurrentResponses++;
@ -213,20 +208,18 @@ namespace CryptoExchange.Net.Sockets
/// <summary>
/// Validate if a message is actually processable by this query
/// </summary>
public virtual bool PreCheckMessage(SocketConnection connection, DataEvent<object> message) => true;
/// <param name="message"></param>
/// <returns></returns>
public virtual bool PreCheckMessage(DataEvent<object> message) => true;
/// <inheritdoc />
public override void Timeout()
{
if (Completed)
return;
Completed = true;
if (TimeoutBehavior == TimeoutBehavior.Fail)
Result = new CallResult<THandlerResponse>(new TimeoutError());
else
Result = new CallResult<THandlerResponse>(default, null, default);
Completed = true;
Result = new CallResult<THandlerResponse>(new TimeoutError());
ContinueAwaiter?.Set();
_event.Set();
}

View File

@ -202,18 +202,6 @@ namespace CryptoExchange.Net.Sockets
}
}
/// <summary>
/// The number of current pending requests
/// </summary>
public int PendingRequests
{
get
{
lock (_listenersLock)
return _listeners.OfType<Query>().Where(x => !x.Completed).Count();
}
}
private bool _pausedActivity;
private readonly object _listenersLock;
private readonly List<IMessageProcessor> _listeners;
@ -531,10 +519,7 @@ namespace CryptoExchange.Net.Sockets
{
// If this message is for this listener then it is automatically confirmed, even if the subscription is not (yet) confirmed
subscriptionProcessor.Confirmed = true;
if (subscriptionProcessor.SubscriptionQuery?.TimeoutBehavior == TimeoutBehavior.Succeed)
// If this subscription has a query waiting for a timeout (success if there is no error response)
// then time it out now as the data is being received, so we assume it's successful
subscriptionProcessor.SubscriptionQuery.Timeout();
// This doesn't trigger a waiting subscribe query, should probably also somehow set the wait event for that
}
// 5. Deserialize the message
@ -1011,7 +996,7 @@ namespace CryptoExchange.Net.Sockets
return result;
}
var subQuery = subscription.CreateSubscriptionQuery(this);
var subQuery = subscription.GetSubQuery(this);
if (subQuery == null)
{
subscription.IsResubscribing = false;
@ -1046,7 +1031,7 @@ namespace CryptoExchange.Net.Sockets
internal async Task UnsubscribeAsync(Subscription subscription)
{
var unsubscribeRequest = subscription.CreateUnsubscriptionQuery(this);
var unsubscribeRequest = subscription.GetUnsubQuery();
if (unsubscribeRequest == null)
return;
@ -1059,7 +1044,7 @@ namespace CryptoExchange.Net.Sockets
if (!_socket.IsOpen)
return new CallResult(new WebError("Socket is not connected"));
var subQuery = subscription.CreateSubscriptionQuery(this);
var subQuery = subscription.GetSubQuery(this);
if (subQuery == null)
return CallResult.SuccessResult;

View File

@ -80,16 +80,6 @@ namespace CryptoExchange.Net.Sockets
/// </summary>
public string? Topic { get; set; }
/// <summary>
/// The subscribe query for this subscription
/// </summary>
public Query? SubscriptionQuery { get; private set; }
/// <summary>
/// The unsubscribe query for this subscription
/// </summary>
public Query? UnsubscriptionQuery { get; private set; }
/// <summary>
/// ctor
/// </summary>
@ -101,21 +91,11 @@ namespace CryptoExchange.Net.Sockets
Id = ExchangeHelpers.NextId();
}
/// <summary>
/// Create a new subscription query
/// </summary>
public Query? CreateSubscriptionQuery(SocketConnection connection)
{
var query = GetSubQuery(connection);
SubscriptionQuery = query;
return query;
}
/// <summary>
/// Get the subscribe query to send when subscribing
/// </summary>
/// <returns></returns>
protected abstract Query? GetSubQuery(SocketConnection connection);
public abstract Query? GetSubQuery(SocketConnection connection);
/// <summary>
/// Handle a subscription query response
@ -129,21 +109,11 @@ namespace CryptoExchange.Net.Sockets
/// <param name="message"></param>
public virtual void HandleUnsubQueryResponse(object message) { }
/// <summary>
/// Create a new unsubscription query
/// </summary>
public Query? CreateUnsubscriptionQuery(SocketConnection connection)
{
var query = GetUnsubQuery(connection);
UnsubscriptionQuery = query;
return query;
}
/// <summary>
/// Get the unsubscribe query to send when unsubscribing
/// </summary>
/// <returns></returns>
protected abstract Query? GetUnsubQuery(SocketConnection connection);
public abstract Query? GetUnsubQuery();
/// <inheritdoc />
public virtual CallResult<object> Deserialize(IMessageAccessor message, Type type) => message.Deserialize(type);

View File

@ -22,9 +22,9 @@ namespace CryptoExchange.Net.Sockets
}
/// <inheritdoc />
protected override Query? GetSubQuery(SocketConnection connection) => null;
public override Query? GetSubQuery(SocketConnection connection) => null;
/// <inheritdoc />
protected override Query? GetUnsubQuery(SocketConnection connection) => null;
public override Query? GetUnsubQuery() => null;
}
}

View File

@ -21,8 +21,6 @@ namespace CryptoExchange.Net.Testing.Implementations
public Uri Uri { get; set; }
public Version HttpVersion { get; set; }
public int RequestId { get; set; }
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.

View File

@ -1,6 +1,5 @@
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Options;
using System;
using System.Net.Http;
@ -15,11 +14,11 @@ namespace CryptoExchange.Net.Testing.Implementations
_request = request;
}
public void Configure(RestExchangeOptions options, HttpClient? client)
{
public void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient = null)
{
}
public IRequest Create(Version httpRequestVersion, HttpMethod method, Uri uri, int requestId)
public IRequest Create(HttpMethod method, Uri uri, int requestId)
{
_request.Method = method;
_request.Uri = uri;
@ -27,6 +26,6 @@ namespace CryptoExchange.Net.Testing.Implementations
return _request;
}
public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout, TimeSpan? httpKeepAliveInterval) {}
public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout) {}
}
}

View File

@ -1,5 +1,4 @@
using CryptoExchange.Net.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
@ -12,7 +11,6 @@ namespace CryptoExchange.Net.Testing.Implementations
private readonly Stream _response;
public HttpStatusCode StatusCode { get; }
public Version HttpVersion { get; }
public bool IsSuccessStatusCode { get; }
@ -23,7 +21,6 @@ namespace CryptoExchange.Net.Testing.Implementations
public TestResponse(HttpStatusCode code, Stream response)
{
StatusCode = code;
HttpVersion = new Version(2, 0);
IsSuccessStatusCode = code == HttpStatusCode.OK;
_response = response;
}

View File

@ -128,7 +128,12 @@ namespace CryptoExchange.Net.Testing
var uriParams = client.ParameterPositions[method] == HttpMethodParameterPosition.InUri ? client.CreateParameterDictionary(parameters) : null;
var bodyParams = client.ParameterPositions[method] == HttpMethodParameterPosition.InBody ? client.CreateParameterDictionary(parameters) : null;
var requestDefinition = new RestRequestConfiguration(
var headers = new Dictionary<string, string>();
authProvider.TimeProvider = new TestAuthTimeProvider(time ?? new DateTime(2024, 01, 01, 0, 0, 0, DateTimeKind.Utc));
authProvider.ProcessRequest(
client,
new RestRequestConfiguration(
new RequestDefinition(path, method)
{
Authenticated = true
@ -136,19 +141,14 @@ namespace CryptoExchange.Net.Testing
host,
uriParams ?? new Dictionary<string, object>(),
bodyParams ?? new Dictionary<string, object>(),
new Dictionary<string, string>(),
headers,
client.ArraySerialization,
client.ParameterPositions[method],
client.RequestBodyFormat
);
authProvider.TimeProvider = new TestAuthTimeProvider(time ?? new DateTime(2024, 01, 01, 0, 0, 0, DateTimeKind.Utc));
authProvider.ProcessRequest(
client,
requestDefinition
)
);
var signature = getSignature(requestDefinition.QueryParameters, requestDefinition.BodyParameters, requestDefinition.Headers);
var signature = getSignature(uriParams, bodyParams, headers);
if (!string.Equals(signature, expectedSignature, compareCase ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase))
throw new Exception($"Signatures do not match. Expected: {expectedSignature}, Actual: {signature}");

View File

@ -38,10 +38,6 @@ Full list of all libraries part of the CryptoExchange.Net ecosystem. Consider us
Any of these can be installed independently or install [CryptoClients.Net](https://github.com/jkorf/CryptoClients.Net) which includes all exchange API's.
### Full demo application
A full demo application is available using the [CryptoClients.Net](https://github.com/jkorf/CryptoClients.Net) library:
https://github.com/JKorf/CryptoManager.Net
## Discord
[![Nuget version](https://img.shields.io/discord/847020490588422145?style=for-the-badge)](https://discord.gg/MSpeEtSY8t)
A Discord server is available [here](https://discord.gg/MSpeEtSY8t). Feel free to join for discussion and/or questions around the CryptoExchange.Net and implementation libraries.
@ -63,19 +59,6 @@ Make a one time donation in a crypto currency of your choice. If you prefer to d
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf).
## Release notes
* Version 9.7.0 - 01 Sep 2025
* Added LibraryHelpers.CreateHttpClientMessageHandle to standardize HttpMessageHandler creation
* Added REST client option for selecting HTTP protocol version
* Added REST client option for HTTP client keep alive interval
* Added HttpVersion to WebCallResult responses
* Updated request logic to default to using HTTP version 2.0 for dotnet core
* Version 9.6.0 - 25 Aug 2025
* Added support for parsing REST response even though status indicates error
* Added better support for subscriptions without subscribe confirmation
* Added check in websocket for receiving 401 unauthorized http response status when 101 was expected
* Removed obsolete attribute on Error.Code property, updated the description
* Version 9.5.0 - 19 Aug 2025
* Added better error handling support
* Added ErrorDescription, ErrorType and IsTransient to Error object