public static async Task InvokeThrottlerSentinelAsync(HttpContext context, IThrottlingCache tc, ThrottlingSentinelOptions options, Action <HttpResponseMessage, HttpResponse> transformer) { var utcNow = DateTime.UtcNow; var throttlingContext = options.ContextResolver?.Invoke(context); if (!throttlingContext.IsNullOrWhiteSpace()) { try { await ThrottleLocker.WaitAsync(); if (!tc.TryGetValue(throttlingContext, out var tr)) { tr = new ThrottleRequest(options.Quota); tc.AddIfNotContainsKey(throttlingContext, tr); } else { tr.Refresh(); tr.Total++; } var window = new TimeRange(utcNow, tr.Expires); var delta = window.Duration; var reset = utcNow.Add(delta); context.Response.Headers.AddOrUpdate(options.RateLimitHeaderName, tr.Quota.RateLimit.ToString(CultureInfo.InvariantCulture)); context.Response.Headers.AddOrUpdate(options.RateLimitRemainingHeaderName, Math.Max(tr.Quota.RateLimit - tr.Total, 0).ToString(CultureInfo.InvariantCulture)); context.Response.Headers.AddOrUpdate(options.RateLimitResetHeaderName, reset.ToEpochTime().ToString(CultureInfo.InvariantCulture)); if (tr.Total > tr.Quota.RateLimit && tr.Expires > utcNow) { var message = options.ResponseBroker?.Invoke(delta, reset); if (message != null) { message.ToHttpResponse(context.Response, transformer); throw new ThrottlingException((int)message.StatusCode, await message.Content.ReadAsStringAsync(), tr.Quota.RateLimit, delta, reset); } } tc[throttlingContext] = tr; } finally { ThrottleLocker.Release(); } } }
/// <summary> /// Initializes a new instance of the <see cref="ThrottlingSentinelFilter"/> class. /// </summary> /// <param name="setup">The <see cref="ThrottlingSentinelOptions" /> which need to be configured.</param> /// <param name="tc">The dependency injected <see cref="IThrottlingCache"/>.</param> public ThrottlingSentinelFilter(IOptions <ThrottlingSentinelOptions> setup, IThrottlingCache tc) : base(setup) { ThrottlingCache = tc; }