using System.Collections.Concurrent; using Microsoft.Extensions.Logging; namespace RdtClient.Service.Handlers; public class RateLimitingHandler : DelegatingHandler { private readonly ConcurrentQueue _callQueue = new(); private readonly TimeSpan _requestWindow; private readonly ILogger _logger; private readonly Int32 _requestsAllowedPerWindow; /// /// Initializes a new instance of the class. /// /// The logger. /// The number of requests allowed per window. /// The time to delay when the maximum number of requests has been reached (The window). public RateLimitingHandler(ILogger logger, Int32 requestsAllowedPerWindow, TimeSpan requestWindow) { ArgumentOutOfRangeException.ThrowIfNegativeOrZero(requestsAllowedPerWindow); ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(requestWindow, TimeSpan.Zero); _logger = logger; _requestsAllowedPerWindow = requestsAllowedPerWindow; _requestWindow = requestWindow; } /// protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var now = DateTimeOffset.UtcNow; _callQueue.Enqueue(now); while (_callQueue.TryPeek(out var oldCall) && now - oldCall > _requestWindow) { _logger.LogDebug("Removing oldest request from queue"); _callQueue.TryDequeue(out _); } if (_callQueue.Count > _requestsAllowedPerWindow) { _logger.LogDebug("Delaying request"); var earliestAllowedRequestTime = now.Add(-_requestWindow); var delayDuration = _callQueue.ElementAtOrDefault(_requestsAllowedPerWindow - 1) - earliestAllowedRequestTime; if (delayDuration > TimeSpan.Zero) { _logger.LogDebug("Delaying request for {DelayDuration}", delayDuration); await Task.Delay(delayDuration, cancellationToken); } } _logger.LogDebug("Sending request"); return await base.SendAsync(request, cancellationToken); } }