Skip to content

Instantly share code, notes, and snippets.

@tstephansen
Created February 28, 2024 18:03
Show Gist options
  • Save tstephansen/52e883086c0abea8aafddc2bcbce2b90 to your computer and use it in GitHub Desktop.
Save tstephansen/52e883086c0abea8aafddc2bcbce2b90 to your computer and use it in GitHub Desktop.
Rate Limiting Handler
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