Created
February 28, 2024 18:03
-
-
Save tstephansen/52e883086c0abea8aafddc2bcbce2b90 to your computer and use it in GitHub Desktop.
Rate Limiting Handler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Concurrent; | |
namespace MyProject.Handlers; | |
/// <summary> | |
/// A handler for limiting the number of requests made per minute. | |
/// </summary> | |
public class RateLimitingHandler : DelegatingHandler | |
{ | |
private readonly ConcurrentQueue<DateTimeOffset> _callQueue = new(); | |
private readonly TimeSpan _requestDelayTime; | |
private readonly ILogger<RateLimitingHandler> _logger; | |
private readonly int _requestsAllowedPerMinute; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="RateLimitingHandler"/> class. | |
/// </summary> | |
/// <param name="logger">The logger.</param> | |
/// <param name="requestsAllowedPerMinute">The number of requests allowed per minute.</param> | |
/// <param name="requestDelayTime">The time to delay when the maximum number of requests has been reached.</param> | |
public RateLimitingHandler(ILogger<RateLimitingHandler> logger, int requestsAllowedPerMinute, TimeSpan requestDelayTime) | |
{ | |
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(requestsAllowedPerMinute); | |
ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(requestDelayTime, TimeSpan.Zero); | |
_logger = logger; | |
_requestsAllowedPerMinute = requestsAllowedPerMinute; | |
_requestDelayTime = requestDelayTime; | |
} | |
/// <inheritdoc/> | |
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, | |
CancellationToken cancellationToken) | |
{ | |
var now = DateTimeOffset.UtcNow; | |
_callQueue.Enqueue(now); | |
while (_callQueue.TryPeek(out var oldCall) && now - oldCall > _requestDelayTime) | |
{ | |
_logger.LogDebug("Removing oldest request from queue"); | |
_callQueue.TryDequeue(out _); | |
} | |
if (_callQueue.Count > _requestsAllowedPerMinute) | |
{ | |
_logger.LogDebug("Delaying request"); | |
var earliestAllowedRequestTime = now.Add(-_requestDelayTime); | |
var delayDuration = _callQueue.ElementAtOrDefault(_requestsAllowedPerMinute - 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); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment