1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-06-07 16:06:15 +00:00

Feature/update settings (#225)

Added SetOptions method to update client settings
Added SocketConnection parameter to PeriodicQuery callback
Added setting of DefaultProxyCredentials on HttpClient instance when client is not provided by DI
Added support for overriding request time out per request
Changed max wait time for close handshake response from 5 seconds to 1 second
Fixed exception in trade tracker when there is no data in the initial snapshot
This commit is contained in:
Jan Korf 2024-12-23 08:49:58 +01:00 committed by GitHub
parent 8605196390
commit 0be1bb16e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 159 additions and 32 deletions

View File

@ -93,6 +93,17 @@ namespace CryptoExchange.Net.Clients
AuthenticationProvider = CreateAuthenticationProvider(credentials.Copy()); AuthenticationProvider = CreateAuthenticationProvider(credentials.Copy());
} }
/// <inheritdoc />
public virtual void SetOptions<T>(UpdateOptions<T> options) where T : ApiCredentials
{
ClientOptions.Proxy = options.Proxy;
ClientOptions.RequestTimeout = options.RequestTimeout ?? ClientOptions.RequestTimeout;
ApiOptions.ApiCredentials = options.ApiCredentials ?? ClientOptions.ApiCredentials;
if (options.ApiCredentials != null)
AuthenticationProvider = CreateAuthenticationProvider(options.ApiCredentials.Copy());
}
/// <summary> /// <summary>
/// Dispose /// Dispose
/// </summary> /// </summary>

View File

@ -961,6 +961,14 @@ namespace CryptoExchange.Net.Clients
/// <returns>Server time</returns> /// <returns>Server time</returns>
protected virtual Task<WebCallResult<DateTime>> GetServerTimestampAsync() => throw new NotImplementedException(); protected virtual Task<WebCallResult<DateTime>> GetServerTimestampAsync() => throw new NotImplementedException();
/// <inheritdoc />
public override void SetOptions<T>(UpdateOptions<T> options)
{
base.SetOptions(options);
RequestFactory.UpdateSettings(options.Proxy, options.RequestTimeout ?? ClientOptions.RequestTimeout);
}
internal async Task<WebCallResult<bool>> SyncTimeAsync() internal async Task<WebCallResult<bool>> SyncTimeAsync()
{ {
var timeSyncParams = GetTimeSyncInfo(); var timeSyncParams = GetTimeSyncInfo();

View File

@ -158,7 +158,7 @@ namespace CryptoExchange.Net.Clients
/// <param name="interval"></param> /// <param name="interval"></param>
/// <param name="queryDelegate"></param> /// <param name="queryDelegate"></param>
/// <param name="callback"></param> /// <param name="callback"></param>
protected virtual void RegisterPeriodicQuery(string identifier, TimeSpan interval, Func<SocketConnection, Query> queryDelegate, Action<CallResult>? callback) protected virtual void RegisterPeriodicQuery(string identifier, TimeSpan interval, Func<SocketConnection, Query> queryDelegate, Action<SocketConnection, CallResult>? callback)
{ {
PeriodicTaskRegistrations.Add(new PeriodicTaskRegistration PeriodicTaskRegistrations.Add(new PeriodicTaskRegistration
{ {
@ -422,9 +422,10 @@ namespace CryptoExchange.Net.Clients
result.Error!.Message = "Authentication failed: " + result.Error.Message; result.Error!.Message = "Authentication failed: " + result.Error.Message;
return new CallResult(result.Error)!; return new CallResult(result.Error)!;
} }
}
_logger.Authenticated(socket.SocketId); _logger.Authenticated(socket.SocketId);
}
socket.Authenticated = true; socket.Authenticated = true;
return new CallResult(null); return new CallResult(null);
} }
@ -710,6 +711,25 @@ namespace CryptoExchange.Net.Clients
return new CallResult(null); return new CallResult(null);
} }
/// <inheritdoc />
public override void SetOptions<T>(UpdateOptions<T> options)
{
var previousProxyIsSet = ClientOptions.Proxy != null;
base.SetOptions(options);
if ((!previousProxyIsSet && options.Proxy == null)
|| !socketConnections.Any())
{
return;
}
_logger.LogInformation("Reconnecting websockets to apply proxy");
// Update proxy, also triggers reconnect
foreach (var connection in socketConnections)
_ = connection.Value.UpdateProxy(options.Proxy);
}
/// <summary> /// <summary>
/// Log the current state of connections and subscriptions /// Log the current state of connections and subscriptions
/// </summary> /// </summary>

View File

@ -1,5 +1,6 @@
using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.SharedApis; using CryptoExchange.Net.SharedApis;
using System; using System;
@ -31,5 +32,12 @@ namespace CryptoExchange.Net.Interfaces
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <param name="credentials"></param> /// <param name="credentials"></param>
void SetApiCredentials<T>(T credentials) where T : ApiCredentials; void SetApiCredentials<T>(T credentials) where T : ApiCredentials;
/// <summary>
/// Set new options. Note that when using a proxy this should be provided in the options even when already set before or it will be reset.
/// </summary>
/// <typeparam name="T">Api crentials type</typeparam>
/// <param name="options">Options to set</param>
void SetOptions<T>(UpdateOptions<T> options) where T : ApiCredentials;
} }
} }

View File

@ -24,6 +24,13 @@ namespace CryptoExchange.Net.Interfaces
/// <param name="requestTimeout">Request timeout to use</param> /// <param name="requestTimeout">Request timeout to use</param>
/// <param name="httpClient">Optional shared http client instance</param> /// <param name="httpClient">Optional shared http client instance</param>
/// <param name="proxy">Optional proxy to use when no http client is provided</param> /// <param name="proxy">Optional proxy to use when no http client is provided</param>
void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient=null); 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>
void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout);
} }
} }

View File

@ -93,5 +93,10 @@ namespace CryptoExchange.Net.Interfaces
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task CloseAsync(); Task CloseAsync();
/// <summary>
/// Update proxy setting
/// </summary>
void UpdateProxy(ApiProxy? proxy);
} }
} }

View File

@ -0,0 +1,29 @@
using CryptoExchange.Net.Authentication;
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.Objects.Options
{
/// <summary>
/// Options to update
/// </summary>
public class UpdateOptions<T> where T : ApiCredentials
{
/// <summary>
/// Proxy setting. Note that if this is not provided any previously set proxy will be reset
/// </summary>
public ApiProxy? Proxy { get; set; }
/// <summary>
/// Api credentials
/// </summary>
public T? ApiCredentials { get; set; }
/// <summary>
/// Request timeout
/// </summary>
public TimeSpan? RequestTimeout { get; set; }
}
/// <inheritdoc />
public class UpdateOptions : UpdateOptions<ApiCredentials> { }
}

View File

@ -17,28 +17,7 @@ namespace CryptoExchange.Net.Requests
public void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? client = null) public void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? client = null)
{ {
if (client == null) if (client == null)
{ client = CreateClient(proxy, requestTimeout);
var handler = new HttpClientHandler();
try
{
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
}
catch (PlatformNotSupportedException) { }
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)
};
}
client = new HttpClient(handler)
{
Timeout = requestTimeout
};
}
_httpClient = client; _httpClient = client;
} }
@ -51,5 +30,37 @@ namespace CryptoExchange.Net.Requests
return new Request(new HttpRequestMessage(method, uri), _httpClient, requestId); return new Request(new HttpRequestMessage(method, uri), _httpClient, requestId);
} }
/// <inheritdoc />
public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout)
{
_httpClient = CreateClient(proxy, requestTimeout);
}
private HttpClient CreateClient(ApiProxy? proxy, TimeSpan requestTimeout)
{
var handler = new HttpClientHandler();
try
{
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
}
catch (PlatformNotSupportedException) { }
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
};
return client;
}
} }
} }

View File

@ -155,6 +155,12 @@ namespace CryptoExchange.Net.Sockets
_baseAddress = $"{Uri.Scheme}://{Uri.Host}"; _baseAddress = $"{Uri.Scheme}://{Uri.Host}";
} }
/// <inheritdoc />
public void UpdateProxy(ApiProxy? proxy)
{
Parameters.Proxy = proxy;
}
/// <inheritdoc /> /// <inheritdoc />
public virtual async Task<CallResult> ConnectAsync() public virtual async Task<CallResult> ConnectAsync()
{ {
@ -435,8 +441,8 @@ namespace CryptoExchange.Net.Sockets
{ {
// Wait until we receive close confirmation // Wait until we receive close confirmation
await Task.Delay(10).ConfigureAwait(false); await Task.Delay(10).ConfigureAwait(false);
if (DateTime.UtcNow - startWait > TimeSpan.FromSeconds(5)) if (DateTime.UtcNow - startWait > TimeSpan.FromSeconds(1))
break; // Wait for max 5 seconds, then just abort the connection break; // Wait for max 1 second, then just abort the connection
} }
} }
} }

View File

@ -23,6 +23,6 @@ namespace CryptoExchange.Net.Sockets
/// <summary> /// <summary>
/// Callback after query /// Callback after query
/// </summary> /// </summary>
public Action<CallResult>? Callback { get; set; } public Action<SocketConnection, CallResult>? Callback { get; set; }
} }
} }

View File

@ -23,6 +23,11 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
public bool Completed { get; set; } public bool Completed { get; set; }
/// <summary>
/// Timeout for the request
/// </summary>
public TimeSpan? RequestTimeout { get; set; }
/// <summary> /// <summary>
/// The number of required responses. Can be more than 1 when for example subscribing multiple symbols streams in a single request, /// 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 /// and each symbol receives it's own confirmation response

View File

@ -11,6 +11,8 @@ using System.Diagnostics;
using CryptoExchange.Net.Clients; using CryptoExchange.Net.Clients;
using CryptoExchange.Net.Logging.Extensions; using CryptoExchange.Net.Logging.Extensions;
using System.Threading; using System.Threading;
using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.Authentication;
namespace CryptoExchange.Net.Sockets namespace CryptoExchange.Net.Sockets
{ {
@ -437,7 +439,7 @@ namespace CryptoExchange.Net.Sockets
return Task.CompletedTask; return Task.CompletedTask;
} }
query.IsSend(ApiClient.ClientOptions.RequestTimeout); query.IsSend(query.RequestTimeout ?? ApiClient.ClientOptions.RequestTimeout);
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -583,6 +585,16 @@ namespace CryptoExchange.Net.Sockets
/// <returns></returns> /// <returns></returns>
public async Task TriggerReconnectAsync() => await _socket.ReconnectAsync().ConfigureAwait(false); public async Task TriggerReconnectAsync() => await _socket.ReconnectAsync().ConfigureAwait(false);
/// <summary>
/// Update the proxy setting and reconnect
/// </summary>
/// <param name="proxy">New proxy setting</param>
public async Task UpdateProxy(ApiProxy? proxy)
{
_socket.UpdateProxy(proxy);
await TriggerReconnectAsync().ConfigureAwait(false);
}
/// <summary> /// <summary>
/// Close the connection /// Close the connection
/// </summary> /// </summary>
@ -988,7 +1000,7 @@ namespace CryptoExchange.Net.Sockets
/// <param name="interval">How often</param> /// <param name="interval">How often</param>
/// <param name="queryDelegate">Method returning the query to send</param> /// <param name="queryDelegate">Method returning the query to send</param>
/// <param name="callback">The callback for processing the response</param> /// <param name="callback">The callback for processing the response</param>
public virtual void QueryPeriodic(string identifier, TimeSpan interval, Func<SocketConnection, Query> queryDelegate, Action<CallResult>? callback) public virtual void QueryPeriodic(string identifier, TimeSpan interval, Func<SocketConnection, Query> queryDelegate, Action<SocketConnection, CallResult>? callback)
{ {
if (queryDelegate == null) if (queryDelegate == null)
throw new ArgumentNullException(nameof(queryDelegate)); throw new ArgumentNullException(nameof(queryDelegate));
@ -1020,7 +1032,7 @@ namespace CryptoExchange.Net.Sockets
try try
{ {
var result = await SendAndWaitQueryAsync(query).ConfigureAwait(false); var result = await SendAndWaitQueryAsync(query).ConfigureAwait(false);
callback?.Invoke(result); callback?.Invoke(this, result);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -25,5 +25,7 @@ namespace CryptoExchange.Net.Testing.Implementations
_request.RequestId = requestId; _request.RequestId = requestId;
return _request; return _request;
} }
public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout) {}
} }
} }

View File

@ -92,5 +92,7 @@ namespace CryptoExchange.Net.Testing.Implementations
public Task ReconnectAsync() => throw new NotImplementedException(); public Task ReconnectAsync() => throw new NotImplementedException();
public void Dispose() { } public void Dispose() { }
public void UpdateProxy(ApiProxy? proxy) => throw new NotImplementedException();
} }
} }

View File

@ -350,6 +350,7 @@ namespace CryptoExchange.Net.Trackers.Trades
_data.Add(item); _data.Add(item);
} }
if (_data.Any())
_firstTimestamp = _data.Min(v => v.Timestamp); _firstTimestamp = _data.Min(v => v.Timestamp);
ApplyWindow(false); ApplyWindow(false);