private async Task <bool> RevertIfRequired(RateLimitingResult rateLimitingResult, HttpActionContext context, RateLimitingRequest request, RateLimitPolicy policy, Decision decision) { if (decision == Decision.REVERTSUCCESSCOST && rateLimitingResult.State == ResultState.Success) { await _rateLimiter.LimitRequestAsync(request, () => RateLimitingFilter.GetCustomAttributes(context), context.Request.Headers.Host, getPolicyFuncAsync : _ => Task.FromResult(policy), onPostLimitFuncAsync : async(rateLimitingRequest, postPolicy, rateLimitingRevertResult) => { if (rateLimitingRevertResult.State == ResultState.Success) { context.Request.Properties[$"RateLimitingResult_{_filterId}"] = rateLimitingRevertResult; } if (OnPostLimitRevert != null) { await OnPostLimitRevert.Invoke(request, postPolicy, rateLimitingRevertResult, context).ConfigureAwait(false); } }, revert : true).ConfigureAwait(false); return(await Task.FromResult(true)); } return(await Task.FromResult(false)); }
private static void TooManyRequests(AuthorizationFilterContext context, RateLimitingResult result, string violatedPolicyName = "") { var throttledResponseParameters = RateLimiter.GetThrottledResponseParameters(result, violatedPolicyName); context.HttpContext.Response.StatusCode = ThrottledResponseParameters.StatusCode; foreach (var header in throttledResponseParameters.RateLimitHeaders.Keys) { context.HttpContext.Response.Headers.Add(header, throttledResponseParameters.RateLimitHeaders[header]); } context.Result = new ContentResult() { Content = throttledResponseParameters.Message }; }
private static async Task TooManyRequests(HttpActionContext context, RateLimitingResult result, string violatedPolicyName = "") { var response = context.Response ?? context.Request.CreateResponse(); var throttledResponseParameters = RateLimiter.GetThrottledResponseParameters(result, violatedPolicyName); response.StatusCode = (HttpStatusCode)ThrottledResponseParameters.StatusCode; foreach (var header in throttledResponseParameters.RateLimitHeaders.Keys) { response.Headers.TryAddWithoutValidation(header, throttledResponseParameters.RateLimitHeaders[header]); } response.ReasonPhrase = throttledResponseParameters.Message; context.Response = response; await Task.FromResult <object>(null); }
private static void AddUpdateRateLimitingSuccessHeaders(HttpContext context, RateLimitingResult result) { var successheaders = new Dictionary <string, string>() { { RateLimitHeaders.TokensRemaining, result.TokensRemaining.ToString() }, { RateLimitHeaders.Limit, result.CacheKey.AllowedConsumptionRate.ToString() } }; foreach (var successheader in successheaders.Keys) { if (context.Response.Headers.ContainsKey(successheader)) { context.Response.Headers[successheader] = new StringValues( context.Response.Headers[successheader].ToArray() .Append(successheaders[successheader]).ToArray()); } else { context.Response.Headers.Add(successheader, new StringValues( new string[] { successheaders[successheader] })); } } }
public async Task <RateLimitingResult> LimitRequestAsync(string requestId, string method, string host, string routeTemplate, IList <AllowedConsumptionRate> allowedCallRates, int costPerCall = 1) { return(await _circuitBreakerPolicy.ExecuteAsync(async() => { if (!_redisConnection.IsConnected) { throw new Exception("Redis is not connected at the moment"); } var redisDb = _redisConnection.GetDatabase(); var redisTransaction = redisDb.CreateTransaction(); var utcNowTicks = _clock?.GetCurrentUtcTimeInTicks() ?? DateTime.UtcNow.Ticks; IList <Func <long> > numberOfRequestsMadePerAllowedCallRateAsync = new List <Func <long> >(); IList <RateLimitCacheKey> cacheKeys = new List <RateLimitCacheKey>(); foreach (var allowedCallRate in allowedCallRates) { if (allowedCallRate.Unit == RateLimitUnit.PerCustomPeriod) { var dateTimeNowUtc = new DateTime(utcNowTicks, DateTimeKind.Utc); GetDateRange(allowedCallRate, dateTimeNowUtc, out DateTime fromUtc, out DateTime toUtc); if (!(dateTimeNowUtc >= fromUtc && dateTimeNowUtc <= toUtc)) { return new RateLimitingResult(ResultState.NotApplicable); } } numberOfRequestsMadePerAllowedCallRateAsync.Add( GetNumberOfRequestsAsync(requestId, method, host, routeTemplate, allowedCallRate, cacheKeys, redisTransaction, utcNowTicks, costPerCall)); } await ExecuteTransactionAsync(redisTransaction).ConfigureAwait(false); var violatedCacheKeys = new SortedList <long, RateLimitCacheKey>(); var minCallsRemaining = int.MaxValue; var minCallsCacheKey = default(RateLimitCacheKey); for (var i = 0; i < allowedCallRates.Count; i++) { var cacheKey = cacheKeys[i]; var callsRemaining = (cacheKey.AllowedConsumptionRate.MaxBurst != 0 ? cacheKey.AllowedConsumptionRate.MaxBurst : cacheKey.Limit) - numberOfRequestsMadePerAllowedCallRateAsync[i](); if (minCallsRemaining > callsRemaining) { minCallsRemaining = callsRemaining > 0 ? (int)callsRemaining : 0; minCallsCacheKey = cacheKey; } if (callsRemaining < 0) { violatedCacheKeys.Add((long)allowedCallRates[i].Unit, cacheKey); } } if (!violatedCacheKeys.Any() || costPerCall <= 0) { return new RateLimitingResult(ResultState.Success, 0, minCallsCacheKey, minCallsRemaining); } var postViolationTransaction = redisDb.CreateTransaction(); if (!_countThrottledRequests) { UndoUnsuccessfulRequestCount(postViolationTransaction, cacheKeys, utcNowTicks, costPerCall); } var violatedCacheKey = violatedCacheKeys.Last().Value; var getOldestRequestTimestampInTicksFunc = GetOldestRequestTimestampInTicksFunc(postViolationTransaction, violatedCacheKey, utcNowTicks); var throttleState = ResultState.Throttled; try { await ExecuteTransactionAsync(postViolationTransaction).ConfigureAwait(false); } catch { if (!_countThrottledRequests) { throttleState = ResultState.ThrottledButCompensationFailed; } } var rateLimitingResult = new RateLimitingResult(throttleState, GetWaitingIntervalInTicks(getOldestRequestTimestampInTicksFunc, violatedCacheKey, utcNowTicks), violatedCacheKey, 0); _onThrottled?.Invoke(rateLimitingResult); return rateLimitingResult; }, new RateLimitingResult(ResultState.LimitApplicationFailed)).ConfigureAwait(false)); }
private static void AddUpdateRateLimitingSuccessHeaders(HttpActionExecutedContext context, RateLimitingResult result) { if (result.State == ResultState.LimitApplicationFailed || result.State == ResultState.NotApplicable) { return; } var successheaders = new Dictionary <string, string>() { { RateLimitHeaders.TokensRemaining, result.TokensRemaining.ToString() }, { RateLimitHeaders.Limit, result.CacheKey.AllowedConsumptionRate?.ToString() } }; if (context.Response == null) { return; } foreach (var successheader in successheaders.Keys) { if (context.Response.Headers.Contains(successheader)) { var successHeaderValues = context.Response.Headers.GetValues(successheader)?.ToList() ?? new List <string>(); successHeaderValues.Add(successheaders[successheader]); context.Response.Headers.Remove(successheader); context.Response.Headers.Add(successheader, successHeaderValues); } else { context.Response.Headers.Add(successheader, new string[] { successheaders[successheader] }); } } }