/// <inheritdoc /> protected override async Task <HttpResponseMessage> ImplementationAsync ( Func <Context, CancellationToken, Task <HttpResponseMessage> > action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext ) { if (!context.TryGetValue("endpoint", out var rawEndpoint) || !(rawEndpoint is string endpoint)) { throw new InvalidOperationException("No endpoint set."); } if (!_rateLimitBuckets.TryGetValue(endpoint, out var rateLimitBucket)) { rateLimitBucket = _globalRateLimitBucket; } var now = DateTime.UtcNow; if (!await rateLimitBucket.TryTakeAsync()) { var rateLimitedResponse = new HttpResponseMessage(HttpStatusCode.TooManyRequests); var delay = rateLimitBucket.ResetsAt - now; rateLimitedResponse.Headers.RetryAfter = new RetryConditionHeaderValue(delay); return(rateLimitedResponse); } // The request can proceed without hitting rate limits, and we've taken a token var requestAction = action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); var response = await requestAction; if (!RateLimitBucket.TryParse(response.Headers, out var newLimits)) { return(response); } if (newLimits.IsGlobal) { if (_globalRateLimitBucket.ResetsAt < newLimits.ResetsAt) { _globalRateLimitBucket = newLimits; } return(response); } _rateLimitBuckets.AddOrUpdate ( endpoint, newLimits, (_, old) => old.ResetsAt < newLimits.ResetsAt ? newLimits : old ); return(response); }
/// <summary> /// Initializes a new instance of the <see cref="DiscordRateLimitPolicy"/> class. /// </summary> private DiscordRateLimitPolicy() { _globalRateLimitBucket = new RateLimitBucket ( 10000, 10000, DateTime.Today + TimeSpan.FromDays(1), "global", true ); _rateLimitBuckets = new ConcurrentDictionary <string, RateLimitBucket>(); }