diff --git a/CryptoExchange.Net/Clients/RestApiClient.cs b/CryptoExchange.Net/Clients/RestApiClient.cs index ace6642..d9fb3f9 100644 --- a/CryptoExchange.Net/Clients/RestApiClient.cs +++ b/CryptoExchange.Net/Clients/RestApiClient.cs @@ -154,6 +154,7 @@ namespace CryptoExchange.Net.Clients /// Cancellation token /// Additional headers for this request /// Override the request weight for this request definition, for example when the weight depends on the parameters + /// Specify the weight to apply to the individual rate limit guard for this request /// protected virtual Task> SendAsync( string baseAddress, @@ -161,7 +162,8 @@ namespace CryptoExchange.Net.Clients ParameterCollection? parameters, CancellationToken cancellationToken, Dictionary? additionalHeaders = null, - int? weight = null) where T : class + int? weight = null, + int? weightSingleLimiter = null) where T : class { var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method]; return SendAsync( @@ -171,7 +173,8 @@ namespace CryptoExchange.Net.Clients parameterPosition == HttpMethodParameterPosition.InBody ? parameters : null, cancellationToken, additionalHeaders, - weight); + weight, + weightSingleLimiter); } /// @@ -185,6 +188,7 @@ namespace CryptoExchange.Net.Clients /// Cancellation token /// Additional headers for this request /// Override the request weight for this request definition, for example when the weight depends on the parameters + /// Specify the weight to apply to the individual rate limit guard for this request /// protected virtual async Task> SendAsync( string baseAddress, @@ -193,7 +197,8 @@ namespace CryptoExchange.Net.Clients ParameterCollection? bodyParameters, CancellationToken cancellationToken, Dictionary? additionalHeaders = null, - int? weight = null) where T : class + int? weight = null, + int? weightSingleLimiter = null) where T : class { string? cacheKey = null; if (ShouldCache(definition)) @@ -217,7 +222,7 @@ namespace CryptoExchange.Net.Clients currentTry++; var requestId = ExchangeHelpers.NextId(); - var prepareResult = await PrepareAsync(requestId, baseAddress, definition, cancellationToken, additionalHeaders, weight).ConfigureAwait(false); + var prepareResult = await PrepareAsync(requestId, baseAddress, definition, cancellationToken, additionalHeaders, weight, weightSingleLimiter).ConfigureAwait(false); if (!prepareResult) return new WebCallResult(prepareResult.Error!); @@ -258,6 +263,7 @@ namespace CryptoExchange.Net.Clients /// Cancellation token /// Additional headers for this request /// Override the request weight for this request + /// Specify the weight to apply to the individual rate limit guard for this request /// /// protected virtual async Task PrepareAsync( @@ -266,10 +272,9 @@ namespace CryptoExchange.Net.Clients RequestDefinition definition, CancellationToken cancellationToken, Dictionary? additionalHeaders = null, - int? weight = null) + int? weight = null, + int? weightSingleLimiter = null) { - var requestWeight = weight ?? definition.Weight; - // Time sync if (definition.Authenticated) { @@ -295,6 +300,7 @@ namespace CryptoExchange.Net.Clients } // Rate limiting + var requestWeight = weight ?? definition.Weight; if (requestWeight != 0) { if (definition.RateLimitGate == null) @@ -316,7 +322,8 @@ namespace CryptoExchange.Net.Clients if (ClientOptions.RateLimiterEnabled) { - var limitResult = await definition.RateLimitGate.ProcessSingleAsync(_logger, requestId, definition.LimitGuard, RateLimitItemType.Request, definition, baseAddress, AuthenticationProvider?._credentials.Key, ClientOptions.RateLimitingBehaviour, cancellationToken).ConfigureAwait(false); + var singleRequestWeight = weightSingleLimiter ?? 1; + var limitResult = await definition.RateLimitGate.ProcessSingleAsync(_logger, requestId, definition.LimitGuard, RateLimitItemType.Request, definition, baseAddress, AuthenticationProvider?._credentials.Key, singleRequestWeight, ClientOptions.RateLimitingBehaviour, cancellationToken).ConfigureAwait(false); if (!limitResult) return new CallResult(limitResult.Error!); } diff --git a/CryptoExchange.Net/Objects/RequestDefinitionCache.cs b/CryptoExchange.Net/Objects/RequestDefinitionCache.cs index acd469f..9c50a0b 100644 --- a/CryptoExchange.Net/Objects/RequestDefinitionCache.cs +++ b/CryptoExchange.Net/Objects/RequestDefinitionCache.cs @@ -58,9 +58,38 @@ namespace CryptoExchange.Net.Objects HttpMethodParameterPosition? parameterPosition = null, ArrayParametersSerialization? arraySerialization = null, bool? preventCaching = null) + => GetOrCreate(method + path, method, path, rateLimitGate, weight, authenticated, limitGuard, requestBodyFormat, parameterPosition, arraySerialization, preventCaching); + + /// + /// Get a definition if it is already in the cache or create a new definition and add it to the cache + /// + /// Request identifier + /// The HttpMethod + /// Endpoint path + /// The rate limit gate + /// The rate limit guard for this specific endpoint + /// Request weight + /// Endpoint is authenticated + /// Request body format + /// Parameter position + /// Array serialization type + /// Prevent request caching + /// + public RequestDefinition GetOrCreate( + string identifier, + HttpMethod method, + string path, + IRateLimitGate? rateLimitGate, + int weight, + bool authenticated, + IRateLimitGuard? limitGuard = null, + RequestBodyFormat? requestBodyFormat = null, + HttpMethodParameterPosition? parameterPosition = null, + ArrayParametersSerialization? arraySerialization = null, + bool? preventCaching = null) { - if (!_definitions.TryGetValue(method + path, out var def)) + if (!_definitions.TryGetValue(identifier, out var def)) { def = new RequestDefinition(path, method) { @@ -73,7 +102,7 @@ namespace CryptoExchange.Net.Objects ParameterPosition = parameterPosition, PreventCaching = preventCaching ?? false }; - _definitions.TryAdd(method + path, def); + _definitions.TryAdd(identifier, def); } return def; diff --git a/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGate.cs b/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGate.cs index 2d76498..e4e7cf2 100644 --- a/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGate.cs +++ b/CryptoExchange.Net/RateLimiting/Interfaces/IRateLimitGate.cs @@ -68,8 +68,9 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces /// The host address /// The API key /// Behaviour when rate limit is hit + /// The weight to apply to the limit guard /// Cancelation token /// Error if RateLimitingBehaviour is Fail and rate limit is hit - Task ProcessSingleAsync(ILogger logger, int itemId, IRateLimitGuard guard, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, RateLimitingBehaviour behaviour, CancellationToken ct); + Task ProcessSingleAsync(ILogger logger, int itemId, IRateLimitGuard guard, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, int requestWeight, RateLimitingBehaviour behaviour, CancellationToken ct); } } diff --git a/CryptoExchange.Net/RateLimiting/RateLimitGate.cs b/CryptoExchange.Net/RateLimiting/RateLimitGate.cs index 4adb4a5..0557c87 100644 --- a/CryptoExchange.Net/RateLimiting/RateLimitGate.cs +++ b/CryptoExchange.Net/RateLimiting/RateLimitGate.cs @@ -69,6 +69,7 @@ namespace CryptoExchange.Net.RateLimiting RequestDefinition definition, string host, string? apiKey, + int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, CancellationToken ct) { @@ -77,7 +78,7 @@ namespace CryptoExchange.Net.RateLimiting _waitingCount++; try { - return await CheckGuardsAsync(new IRateLimitGuard[] { guard }, logger, itemId, type, definition, host, apiKey, 1, rateLimitingBehaviour, ct).ConfigureAwait(false); + return await CheckGuardsAsync(new IRateLimitGuard[] { guard }, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, ct).ConfigureAwait(false); } catch (TaskCanceledException) {