diff --git a/CryptoExchange.Net/Clients/BaseApiClient.cs b/CryptoExchange.Net/Clients/BaseApiClient.cs index f47c8fd..8d1889b 100644 --- a/CryptoExchange.Net/Clients/BaseApiClient.cs +++ b/CryptoExchange.Net/Clients/BaseApiClient.cs @@ -93,6 +93,17 @@ namespace CryptoExchange.Net.Clients AuthenticationProvider = CreateAuthenticationProvider(credentials.Copy()); } + /// + public virtual void SetOptions(UpdateOptions 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()); + } + /// /// Dispose /// diff --git a/CryptoExchange.Net/Clients/RestApiClient.cs b/CryptoExchange.Net/Clients/RestApiClient.cs index 41e22d1..91e999a 100644 --- a/CryptoExchange.Net/Clients/RestApiClient.cs +++ b/CryptoExchange.Net/Clients/RestApiClient.cs @@ -961,6 +961,14 @@ namespace CryptoExchange.Net.Clients /// Server time protected virtual Task> GetServerTimestampAsync() => throw new NotImplementedException(); + /// + public override void SetOptions(UpdateOptions options) + { + base.SetOptions(options); + + RequestFactory.UpdateSettings(options.Proxy, options.RequestTimeout ?? ClientOptions.RequestTimeout); + } + internal async Task> SyncTimeAsync() { var timeSyncParams = GetTimeSyncInfo(); diff --git a/CryptoExchange.Net/Clients/SocketApiClient.cs b/CryptoExchange.Net/Clients/SocketApiClient.cs index d27ee3d..1adcba6 100644 --- a/CryptoExchange.Net/Clients/SocketApiClient.cs +++ b/CryptoExchange.Net/Clients/SocketApiClient.cs @@ -158,7 +158,7 @@ namespace CryptoExchange.Net.Clients /// /// /// - protected virtual void RegisterPeriodicQuery(string identifier, TimeSpan interval, Func queryDelegate, Action? callback) + protected virtual void RegisterPeriodicQuery(string identifier, TimeSpan interval, Func queryDelegate, Action? callback) { PeriodicTaskRegistrations.Add(new PeriodicTaskRegistration { @@ -422,9 +422,10 @@ namespace CryptoExchange.Net.Clients result.Error!.Message = "Authentication failed: " + result.Error.Message; return new CallResult(result.Error)!; } + + _logger.Authenticated(socket.SocketId); } - _logger.Authenticated(socket.SocketId); socket.Authenticated = true; return new CallResult(null); } @@ -710,6 +711,25 @@ namespace CryptoExchange.Net.Clients return new CallResult(null); } + /// + public override void SetOptions(UpdateOptions 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); + } + /// /// Log the current state of connections and subscriptions /// diff --git a/CryptoExchange.Net/Interfaces/IBaseApiClient.cs b/CryptoExchange.Net/Interfaces/IBaseApiClient.cs index 548a196..9b321b1 100644 --- a/CryptoExchange.Net/Interfaces/IBaseApiClient.cs +++ b/CryptoExchange.Net/Interfaces/IBaseApiClient.cs @@ -1,5 +1,6 @@ using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Options; using CryptoExchange.Net.SharedApis; using System; @@ -31,5 +32,12 @@ namespace CryptoExchange.Net.Interfaces /// /// void SetApiCredentials(T credentials) where T : ApiCredentials; + + /// + /// 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. + /// + /// Api crentials type + /// Options to set + void SetOptions(UpdateOptions options) where T : ApiCredentials; } } \ No newline at end of file diff --git a/CryptoExchange.Net/Interfaces/IRequestFactory.cs b/CryptoExchange.Net/Interfaces/IRequestFactory.cs index 66ff906..f3cc827 100644 --- a/CryptoExchange.Net/Interfaces/IRequestFactory.cs +++ b/CryptoExchange.Net/Interfaces/IRequestFactory.cs @@ -24,6 +24,13 @@ namespace CryptoExchange.Net.Interfaces /// Request timeout to use /// Optional shared http client instance /// Optional proxy to use when no http client is provided - void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient=null); + void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient = null); + + /// + /// Update settings + /// + /// Proxy to use + /// Request timeout to use + void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout); } } diff --git a/CryptoExchange.Net/Interfaces/IWebsocket.cs b/CryptoExchange.Net/Interfaces/IWebsocket.cs index b1e75cb..721688d 100644 --- a/CryptoExchange.Net/Interfaces/IWebsocket.cs +++ b/CryptoExchange.Net/Interfaces/IWebsocket.cs @@ -93,5 +93,10 @@ namespace CryptoExchange.Net.Interfaces /// /// Task CloseAsync(); + + /// + /// Update proxy setting + /// + void UpdateProxy(ApiProxy? proxy); } } diff --git a/CryptoExchange.Net/Objects/Options/UpdateOptions.cs b/CryptoExchange.Net/Objects/Options/UpdateOptions.cs new file mode 100644 index 0000000..9fc9ed0 --- /dev/null +++ b/CryptoExchange.Net/Objects/Options/UpdateOptions.cs @@ -0,0 +1,29 @@ +using CryptoExchange.Net.Authentication; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.Objects.Options +{ + /// + /// Options to update + /// + public class UpdateOptions where T : ApiCredentials + { + /// + /// Proxy setting. Note that if this is not provided any previously set proxy will be reset + /// + public ApiProxy? Proxy { get; set; } + /// + /// Api credentials + /// + public T? ApiCredentials { get; set; } + /// + /// Request timeout + /// + public TimeSpan? RequestTimeout { get; set; } + } + + /// + public class UpdateOptions : UpdateOptions { } +} diff --git a/CryptoExchange.Net/Requests/RequestFactory.cs b/CryptoExchange.Net/Requests/RequestFactory.cs index 83ea61d..693e91d 100644 --- a/CryptoExchange.Net/Requests/RequestFactory.cs +++ b/CryptoExchange.Net/Requests/RequestFactory.cs @@ -17,28 +17,7 @@ namespace CryptoExchange.Net.Requests public void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? client = null) { if (client == null) - { - 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 - }; - } + client = CreateClient(proxy, requestTimeout); _httpClient = client; } @@ -51,5 +30,37 @@ namespace CryptoExchange.Net.Requests return new Request(new HttpRequestMessage(method, uri), _httpClient, requestId); } + + /// + 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; + } } } diff --git a/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs b/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs index fd3fb65..1d364eb 100644 --- a/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs +++ b/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs @@ -155,6 +155,12 @@ namespace CryptoExchange.Net.Sockets _baseAddress = $"{Uri.Scheme}://{Uri.Host}"; } + /// + public void UpdateProxy(ApiProxy? proxy) + { + Parameters.Proxy = proxy; + } + /// public virtual async Task ConnectAsync() { @@ -435,8 +441,8 @@ namespace CryptoExchange.Net.Sockets { // Wait until we receive close confirmation await Task.Delay(10).ConfigureAwait(false); - if (DateTime.UtcNow - startWait > TimeSpan.FromSeconds(5)) - break; // Wait for max 5 seconds, then just abort the connection + if (DateTime.UtcNow - startWait > TimeSpan.FromSeconds(1)) + break; // Wait for max 1 second, then just abort the connection } } } diff --git a/CryptoExchange.Net/Sockets/PeriodicTaskRegistration.cs b/CryptoExchange.Net/Sockets/PeriodicTaskRegistration.cs index 8f9ccaf..9c532bb 100644 --- a/CryptoExchange.Net/Sockets/PeriodicTaskRegistration.cs +++ b/CryptoExchange.Net/Sockets/PeriodicTaskRegistration.cs @@ -23,6 +23,6 @@ namespace CryptoExchange.Net.Sockets /// /// Callback after query /// - public Action? Callback { get; set; } + public Action? Callback { get; set; } } } diff --git a/CryptoExchange.Net/Sockets/Query.cs b/CryptoExchange.Net/Sockets/Query.cs index 833abf8..397ae43 100644 --- a/CryptoExchange.Net/Sockets/Query.cs +++ b/CryptoExchange.Net/Sockets/Query.cs @@ -23,6 +23,11 @@ namespace CryptoExchange.Net.Sockets /// public bool Completed { get; set; } + /// + /// Timeout for the request + /// + public TimeSpan? RequestTimeout { get; set; } + /// /// 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 diff --git a/CryptoExchange.Net/Sockets/SocketConnection.cs b/CryptoExchange.Net/Sockets/SocketConnection.cs index 3e4364f..a1deaaa 100644 --- a/CryptoExchange.Net/Sockets/SocketConnection.cs +++ b/CryptoExchange.Net/Sockets/SocketConnection.cs @@ -11,6 +11,8 @@ using System.Diagnostics; using CryptoExchange.Net.Clients; using CryptoExchange.Net.Logging.Extensions; using System.Threading; +using CryptoExchange.Net.Objects.Options; +using CryptoExchange.Net.Authentication; namespace CryptoExchange.Net.Sockets { @@ -437,7 +439,7 @@ namespace CryptoExchange.Net.Sockets return Task.CompletedTask; } - query.IsSend(ApiClient.ClientOptions.RequestTimeout); + query.IsSend(query.RequestTimeout ?? ApiClient.ClientOptions.RequestTimeout); return Task.CompletedTask; } @@ -583,6 +585,16 @@ namespace CryptoExchange.Net.Sockets /// public async Task TriggerReconnectAsync() => await _socket.ReconnectAsync().ConfigureAwait(false); + /// + /// Update the proxy setting and reconnect + /// + /// New proxy setting + public async Task UpdateProxy(ApiProxy? proxy) + { + _socket.UpdateProxy(proxy); + await TriggerReconnectAsync().ConfigureAwait(false); + } + /// /// Close the connection /// @@ -988,7 +1000,7 @@ namespace CryptoExchange.Net.Sockets /// How often /// Method returning the query to send /// The callback for processing the response - public virtual void QueryPeriodic(string identifier, TimeSpan interval, Func queryDelegate, Action? callback) + public virtual void QueryPeriodic(string identifier, TimeSpan interval, Func queryDelegate, Action? callback) { if (queryDelegate == null) throw new ArgumentNullException(nameof(queryDelegate)); @@ -1020,7 +1032,7 @@ namespace CryptoExchange.Net.Sockets try { var result = await SendAndWaitQueryAsync(query).ConfigureAwait(false); - callback?.Invoke(result); + callback?.Invoke(this, result); } catch (Exception ex) { diff --git a/CryptoExchange.Net/Testing/Implementations/TestRequestFactory.cs b/CryptoExchange.Net/Testing/Implementations/TestRequestFactory.cs index 6bcd8c1..293c2ea 100644 --- a/CryptoExchange.Net/Testing/Implementations/TestRequestFactory.cs +++ b/CryptoExchange.Net/Testing/Implementations/TestRequestFactory.cs @@ -25,5 +25,7 @@ namespace CryptoExchange.Net.Testing.Implementations _request.RequestId = requestId; return _request; } + + public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout) {} } } diff --git a/CryptoExchange.Net/Testing/Implementations/TestSocket.cs b/CryptoExchange.Net/Testing/Implementations/TestSocket.cs index b65cfa3..bec8b8d 100644 --- a/CryptoExchange.Net/Testing/Implementations/TestSocket.cs +++ b/CryptoExchange.Net/Testing/Implementations/TestSocket.cs @@ -92,5 +92,7 @@ namespace CryptoExchange.Net.Testing.Implementations public Task ReconnectAsync() => throw new NotImplementedException(); public void Dispose() { } + + public void UpdateProxy(ApiProxy? proxy) => throw new NotImplementedException(); } } diff --git a/CryptoExchange.Net/Trackers/Trades/TradeTracker.cs b/CryptoExchange.Net/Trackers/Trades/TradeTracker.cs index 8962566..05bc209 100644 --- a/CryptoExchange.Net/Trackers/Trades/TradeTracker.cs +++ b/CryptoExchange.Net/Trackers/Trades/TradeTracker.cs @@ -350,7 +350,8 @@ namespace CryptoExchange.Net.Trackers.Trades _data.Add(item); } - _firstTimestamp = _data.Min(v => v.Timestamp); + if (_data.Any()) + _firstTimestamp = _data.Min(v => v.Timestamp); ApplyWindow(false); }