diff --git a/CryptoExchange.Net/Clients/BaseApiClient.cs b/CryptoExchange.Net/Clients/BaseApiClient.cs index f47c8fdf..8d1889be 100644 --- a/CryptoExchange.Net/Clients/BaseApiClient.cs +++ b/CryptoExchange.Net/Clients/BaseApiClient.cs @@ -93,6 +93,17 @@ public void SetApiCredentials(T credentials) where T : ApiCredentials 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 41e22d16..91e999a9 100644 --- a/CryptoExchange.Net/Clients/RestApiClient.cs +++ b/CryptoExchange.Net/Clients/RestApiClient.cs @@ -961,6 +961,14 @@ protected internal IDictionary CreateParameterDictionary(IDictio /// 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 d27ee3df..1adcba6d 100644 --- a/CryptoExchange.Net/Clients/SocketApiClient.cs +++ b/CryptoExchange.Net/Clients/SocketApiClient.cs @@ -158,7 +158,7 @@ protected virtual void SetDedicatedConnection(string url, bool auth) /// /// /// - 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 @@ public virtual async Task AuthenticateSocketAsync(SocketConnection s 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 @@ public virtual async Task PrepareConnectionsAsync() 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 548a196c..9b321b11 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 @@ public interface IBaseApiClient /// /// 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 66ff9067..f3cc827c 100644 --- a/CryptoExchange.Net/Interfaces/IRequestFactory.cs +++ b/CryptoExchange.Net/Interfaces/IRequestFactory.cs @@ -24,6 +24,13 @@ public interface IRequestFactory /// 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 b1e75cb7..721688df 100644 --- a/CryptoExchange.Net/Interfaces/IWebsocket.cs +++ b/CryptoExchange.Net/Interfaces/IWebsocket.cs @@ -93,5 +93,10 @@ public interface IWebsocket: IDisposable /// /// 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 00000000..9fc9ed04 --- /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 83ea61df..693e91d5 100644 --- a/CryptoExchange.Net/Requests/RequestFactory.cs +++ b/CryptoExchange.Net/Requests/RequestFactory.cs @@ -17,28 +17,7 @@ public class RequestFactory : IRequestFactory 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 @@ public IRequest Create(HttpMethod method, Uri uri, int requestId) 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 fd3fb650..1d364ebd 100644 --- a/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs +++ b/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs @@ -155,6 +155,12 @@ public CryptoExchangeWebSocketClient(ILogger logger, WebSocketParameters websock _baseAddress = $"{Uri.Scheme}://{Uri.Host}"; } + /// + public void UpdateProxy(ApiProxy? proxy) + { + Parameters.Proxy = proxy; + } + /// public virtual async Task ConnectAsync() { @@ -435,8 +441,8 @@ private async Task CloseInternalAsync() { // 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 8f9ccaf7..9c532bba 100644 --- a/CryptoExchange.Net/Sockets/PeriodicTaskRegistration.cs +++ b/CryptoExchange.Net/Sockets/PeriodicTaskRegistration.cs @@ -23,6 +23,6 @@ public class PeriodicTaskRegistration /// /// 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 833abf8b..397ae433 100644 --- a/CryptoExchange.Net/Sockets/Query.cs +++ b/CryptoExchange.Net/Sockets/Query.cs @@ -23,6 +23,11 @@ public abstract class Query : IMessageProcessor /// 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 3e4364f7..a1deaaa6 100644 --- a/CryptoExchange.Net/Sockets/SocketConnection.cs +++ b/CryptoExchange.Net/Sockets/SocketConnection.cs @@ -11,6 +11,8 @@ 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 @@ protected virtual Task HandleRequestSentAsync(int requestId) return Task.CompletedTask; } - query.IsSend(ApiClient.ClientOptions.RequestTimeout); + query.IsSend(query.RequestTimeout ?? ApiClient.ClientOptions.RequestTimeout); return Task.CompletedTask; } @@ -583,6 +585,16 @@ protected virtual async Task HandleStreamMessage(WebSocketMessageType type, Read /// 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 @@ internal async Task ResubscribeAsync(Subscription subscription) /// 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 @@ public virtual void QueryPeriodic(string identifier, TimeSpan interval, Func(T data) 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 89625668..05bc2090 100644 --- a/CryptoExchange.Net/Trackers/Trades/TradeTracker.cs +++ b/CryptoExchange.Net/Trackers/Trades/TradeTracker.cs @@ -350,7 +350,8 @@ protected void SetInitialData(IEnumerable data) _data.Add(item); } - _firstTimestamp = _data.Min(v => v.Timestamp); + if (_data.Any()) + _firstTimestamp = _data.Min(v => v.Timestamp); ApplyWindow(false); }