public override Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitRule rule, string retryAfter) { var message = string.IsNullOrWhiteSpace(_options.QuotaExceededMessage) ? $"Slow down! Too many requests. Try again in {rule.Period}." : _options.QuotaExceededMessage; httpContext.Response.Headers["Retry-After"] = retryAfter; httpContext.Response.StatusCode = _options.HttpStatusCode; httpContext.Response.ContentType = "application/json"; var errorModel = new ErrorResponseModel { Message = message }; return httpContext.Response.WriteAsync(JsonConvert.SerializeObject(errorModel)); }
public override void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule) { base.LogBlockedRequest(httpContext, identity, counter, rule); var key = $"blockedIp_{identity.ClientIp}"; int blockedCount; _memoryCache.TryGetValue(key, out blockedCount); blockedCount++; if(blockedCount > 10) { _blockIpService.BlockIpAsync(identity.ClientIp, false); _logger.LogDebug("Blocked " + identity.ClientIp); } else { _memoryCache.Set(key, blockedCount, new MemoryCacheEntryOptions().SetSlidingExpiration(new System.TimeSpan(0, 5, 0))); } }
public int RetryAfterFrom(DateTime timestamp, RateLimitRule rule) { return(_core.RetryAfterFrom(timestamp, rule)); }
public virtual async Task <RateLimitCounter> ProcessRequestAsync(ClientRequestIdentity requestIdentity, RateLimitRule rule, CancellationToken cancellationToken = default) { var counter = new RateLimitCounter { Timestamp = DateTime.UtcNow, Count = 1 }; var counterId = BuildCounterKey(requestIdentity, rule); // serial reads and writes on same key using (await AsyncLock.WriterLockAsync(counterId).ConfigureAwait(false)) { var cache = _cacheManager.GetCache(nameof(RateLimitCounter)); var entry = await cache.GetOrDefaultAsync <string, RateLimitCounter?>(counterId); if (entry.HasValue) { // entry has not expired if (entry.Value.Timestamp + rule.PeriodTimespan.Value >= DateTime.UtcNow) { // increment request count var totalCount = entry.Value.Count + _config.RateIncrementer?.Invoke() ?? 1; // deep copy counter = new RateLimitCounter { Timestamp = entry.Value.Timestamp, Count = totalCount }; } } // stores: id (string) - timestamp (datetime) - total_requests (long) await cache.SetAsync(counterId, counter, rule.PeriodTimespan.Value); } return(counter); }
public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule, DownstreamRoute downstreamRoute) { _logger.LogInformation(@$ "Request { identity.HttpVerb}:{ identity.Path} from ClientId { identity.ClientId} has been blocked, quota { rule.Limit}/{ rule.Period} exceeded by { counter.TotalRequests}. Blocked by rule ."); }
/// <summary> /// /// </summary> /// <param name="target"></param> /// <param name="rule"></param> /// <returns></returns> protected override async Task <RuleCheckResult> CheckSingleRuleAsync(string target, RateLimitRule rule, RateLimitTypeAttributeJson rateLimitAttrData = null) { var currentRule = rule as SlidingWindowRule; var amount = 1; #region local attribute if (rateLimitAttrData != null && rateLimitAttrData.SlidingWindowLimitAttribute != null) { currentRule.LimitNumber = rateLimitAttrData.SlidingWindowLimitAttribute.LimitNumber; currentRule.StatWindow = CommonUtils.Parse(rateLimitAttrData.SlidingWindowLimitAttribute.StatWindowPeriod); currentRule.StatPeriod = CommonUtils.Parse(rateLimitAttrData.SlidingWindowLimitAttribute.StatSmallPeriod); currentRule.PeriodNumber = (int)(currentRule.StatWindow.TotalMilliseconds / currentRule.StatPeriod.TotalMilliseconds); } #endregion local attribute var currentTime = await _timeProvider.GetCurrentUtcMillisecondsAsync(); var startTime = AlgorithmStartTime.ToSpecifiedTypeTime(currentTime, currentRule.StatWindow, currentRule.StartTimeType); long expireMilliseconds = ((long)currentRule.StatWindow.TotalMilliseconds) * 2; long periodMilliseconds = (long)currentRule.StatPeriod.TotalMilliseconds; var ret = (long[]) await EvaluateScriptAsync(_slidingWindowIncrementLuaScript, new RedisKey[] { target }, new RedisValue[] { amount, expireMilliseconds, periodMilliseconds, currentRule.PeriodNumber, currentTime, startTime, currentRule.LimitNumber, currentRule.LockSeconds }); return(new RuleCheckResult() { IsLimit = ret[0] == 0 ? false : true, Target = target, Count = ret[1], Rule = rule, RateLimitExceptionThrow = currentRule.RateLimitExceptionThrow }); }
/// <summary> /// check single rule for target /// </summary> /// <param name="target"></param> /// <param name="rule"></param> /// <param name="rateLimitAttrData"></param> /// <returns></returns> protected override async Task <RuleCheckResult> CheckSingleRuleAsync(string target, RateLimitRule rule, RateLimitTypeAttributeJson rateLimitAttrData = null) { return(await Task.FromResult(CheckSingleRule(target, rule, rateLimitAttrData))); }
public RateLimitCounter?GetStoredRateLimitCounter(ClientRequestIdentity requestIdentity, RateLimitRule rule) { var counterId = ComputeCounterKey(requestIdentity, rule); // serial reads and writes lock (_processLocker) { var entry = _counterStore.Get(counterId); if (entry.HasValue) { // entry has not expired if (entry.Value.Timestamp + rule.PeriodTimespan.Value >= DateTime.UtcNow) { // deep copy var counter = new RateLimitCounter { Timestamp = entry.Value.Timestamp, TotalRequests = entry.Value.TotalRequests }; return(counter); } } } return(null); }
public RateLimitCounter GetCurrentRateLimitCounter(ClientRequestIdentity requestIdentity, RateLimitRule rule) { var counter = new RateLimitCounter { Timestamp = DateTime.UtcNow, TotalRequests = 0 }; var counterId = ComputeCounterKey(requestIdentity, rule); // serial reads and writes lock (_processLocker) { var entry = _counterStore.Get(counterId); if (entry.HasValue) { // deep copy counter = new RateLimitCounter { Timestamp = entry.Value.Timestamp, TotalRequests = entry.Value.TotalRequests }; } } return(counter); }
public virtual async Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitRule rule, string retryAfter) { var message = string.IsNullOrEmpty(RateLimitClientsRule.Settings.QuotaExceededMessage) ? $"API calls quota exceeded! maximum admitted {rule.Limit} per {rule.Period}." : RateLimitClientsRule.Settings.QuotaExceededMessage; if (!RateLimitClientsRule.Settings.DisableRateLimitHeaders) { httpContext.Response.Headers["Retry-After"] = retryAfter; } httpContext.Response.StatusCode = RateLimitClientsRule.Settings.HttpStatusCode; httpContext.Response.WriteAsync(message); }
/// <summary> /// check single rule for target /// </summary> /// <param name="target"></param> /// <param name="rule"></param> /// <returns></returns> protected override async Task <RuleCheckResult> CheckSingleRuleAsync(string target, RateLimitRule rule) { return(await Task.FromResult(CheckSingleRule(target, rule))); }
public RateLimitHeaders GetRateLimitHeaders(ClientRequestIdentity requestIdentity, RateLimitRule rule) { return(_core.GetRateLimitHeaders(requestIdentity, rule)); }
public string Build(ClientRequestIdentity requestIdentity, RateLimitRule rule) { return($"_{requestIdentity.HttpVerb}_{requestIdentity.Path}"); }
public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitRule rule) { return(_core.ProcessRequest(requestIdentity, rule)); }
/// <summary> /// async check single rule for target /// </summary> /// <param name="target"></param> /// <param name="rule"></param> /// <returns></returns> protected override async Task <RuleCheckResult> CheckSingleRuleAsync(string target, RateLimitRule rule) { var currentRule = rule as TokenBucketRule; var amount = 1; var inflowUnit = currentRule.InflowUnit.TotalMilliseconds; var currentTime = await _timeProvider.GetCurrentUtcMillisecondsAsync(); var startTime = AlgorithmStartTime.ToSpecifiedTypeTime(currentTime, TimeSpan.FromMilliseconds(inflowUnit), currentRule.StartTimeType); var ret = (long[]) await EvaluateScriptAsync(_tokenBucketDecrementLuaScript, new RedisKey[] { target }, new RedisValue[] { amount, currentRule.Capacity, inflowUnit, currentRule.InflowQuantityPerUnit, currentTime, startTime, currentRule.LockSeconds }); var result = new Tuple <bool, long>(ret[0] == 0 ? false : true, ret[1]); return(new RuleCheckResult() { IsLimit = ret[0] == 0 ? false : true, Target = target, Count = ret[1], Rule = rule }); }
public virtual RateLimitHeaders GetRateLimitHeaders(RateLimitCounter?counter, RateLimitRule rule, CancellationToken cancellationToken = default) { var headers = new RateLimitHeaders(); double remaining; DateTime reset; if (counter.HasValue) { reset = counter.Value.Timestamp + (rule.PeriodTimespan ?? rule.Period.ToTimeSpan()); remaining = rule.Limit - counter.Value.Count; } else { reset = DateTime.UtcNow + (rule.PeriodTimespan ?? rule.Period.ToTimeSpan()); remaining = rule.Limit; } headers.Reset = reset.ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo); headers.Limit = rule.Period; headers.Remaining = remaining.ToString(); return(headers); }
public override Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitRule rule, string retryAfter) { httpContext.Response.Headers.Append("Access-Control-Allow-Origin", "*"); return(base.ReturnQuotaExceededResponse(httpContext, rule, retryAfter)); }
public override async Task <RateLimitCounter> ProcessRequestAsync(ClientRequestIdentity requestIdentity, RateLimitRule rule, ICounterKeyBuilder counterKeyBuilder, RateLimitOptions rateLimitOptions, CancellationToken cancellationToken = default) { var counterId = BuildCounterKey(requestIdentity, rule, counterKeyBuilder, rateLimitOptions); return(await IncrementAsync(counterId, rule.PeriodTimespan.Value, _config.RateIncrementer)); }
public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule, DownstreamReRoute downstreamReRoute) { Logger.LogInformation( $"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { downstreamReRoute.UpstreamPathTemplate.OriginalValue }, TraceIdentifier {httpContext.TraceIdentifier}."); }
public void RuleKeyGenIsCorrect() { var rule = new RateLimitRule(HttpVerb.Post, @"/Dummy", 1, 2, 3); Assert.AreEqual("post-/dummy", rule.RuleKey, "Rule key incorrect"); }
public RateLimitHeaders GetRateLimitHeaders(ClientRequestIdentity requestIdentity, RateLimitRule rule) { var headers = new RateLimitHeaders(); var counterId = ComputeCounterKey(requestIdentity, rule); var entry = _counterStore.Get(counterId); if (entry.HasValue) { headers.Reset = (entry.Value.Timestamp + ConvertToTimeSpan(rule.Period)).ToUniversalTime() .ToString("o", DateTimeFormatInfo.InvariantInfo); headers.Limit = rule.Period; headers.Remaining = (rule.Limit - entry.Value.TotalRequests).ToString(); } else { headers.Reset = (DateTime.UtcNow + ConvertToTimeSpan(rule.Period)).ToUniversalTime() .ToString("o", DateTimeFormatInfo.InvariantInfo); headers.Limit = rule.Period; headers.Remaining = rule.Limit.ToString(); } return(headers); }
public void NegativePerDayUnitRaisesArgumentException() { var rule = new RateLimitRule(HttpVerb.Post, @"/Dummy", 1, 2, -3); }
public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitRule rule) { var counter = new RateLimitCounter { Timestamp = DateTime.UtcNow, TotalRequests = 1 }; var counterId = ComputeCounterKey(requestIdentity, rule); // serial reads and writes lock (_processLocker) { var entry = _counterStore.Get(counterId); if (entry.HasValue) { // entry has not expired if (entry.Value.Timestamp + rule.PeriodTimespan.Value >= DateTime.UtcNow) { // increment request count var totalRequests = entry.Value.TotalRequests + 1; // deep copy counter = new RateLimitCounter { Timestamp = entry.Value.Timestamp, TotalRequests = totalRequests }; } } // stores: id (string) - timestamp (datetime) - total_requests (long) _counterStore.Set(counterId, counter, rule.PeriodTimespan.Value); } return(counter); }
protected override void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule) { _logger.LogInformation($"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.Count}. Blocked by rule {rule.Endpoint}, TraceIdentifier {httpContext.TraceIdentifier}."); }
/// <summary> /// async check single rule for target /// </summary> /// <param name="target"></param> /// <param name="rule"></param> /// <returns></returns> protected override async Task <RuleCheckResult> CheckSingleRuleAsync(string target, RateLimitRule rule) { var currentRule = rule as FixedWindowRule; var amount = 1; long expireTime = (long)currentRule.StatWindow.TotalMilliseconds; if (currentRule.StartTimeType == StartTimeType.FromNaturalPeriodBeign) { DateTimeOffset now = await _timeProvider.GetCurrentUtcTimeAsync().ConfigureAwait(false); expireTime = GetExpireTimeFromNaturalPeriodBeign(currentRule.StatWindow, now); } var ret = (long[]) await EvaluateScriptAsync(_fixedWindowIncrementLuaScript, new RedisKey[] { target }, new RedisValue[] { amount, expireTime, currentRule.LimitNumber, currentRule.LockSeconds }).ConfigureAwait(false); return(new RuleCheckResult() { IsLimit = ret[0] == 0 ? false : true, Target = target, Count = ret[1], Rule = rule }); }
protected abstract void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule);
/// <summary> /// 日志 /// </summary> /// <param name="requestIdentity"></param> /// <param name="counter"></param> /// <param name="rule"></param> public void LogBlockedRequest(RequestIdentity requestIdentity, RateLimitCounter counter, RateLimitRule rule) { var log = $"{requestIdentity.RequestPath}\r\n已限制来自[{requestIdentity.PolicyType.ToString()}]{requestIdentity.Value}的请求 {requestIdentity.HttpVerb}:{requestIdentity.Path}\r\n匹配规则: {rule.Endpoint},{rule.Limit}/{rule.Period}\r\n计数器: [{ComputeCounterKey(requestIdentity, rule)}] {counter.TotalRequests},{counter.Timestamp.ToString("yyyy-MM-dd HH:mm:ss")}"; _options.LogHandler.Invoke(log); }
protected virtual string BuildCounterKey(ClientRequestIdentity requestIdentity, RateLimitRule rule) { var key = _counterKeyBuilder.Build(requestIdentity, rule); if (_options.EnableEndpointRateLimiting && _config.EndpointCounterKeyBuilder != null) { key += _config.EndpointCounterKeyBuilder.Build(requestIdentity, rule); } var bytes = Encoding.UTF8.GetBytes(key); using (var algorithm = new SHA1Managed()) { var hash = algorithm.ComputeHash(bytes); return(Convert.ToBase64String(hash)); } }
public string Build(ClientRequestIdentity requestIdentity, RateLimitRule rule) { return($"{_options.RateLimitCounterPrefix}_{requestIdentity.ClientIp}_{rule.Period}"); }
public RateLimitOptionsBuilder WithRateLimitRule(RateLimitRule rateLimitRule) { _rateLimitRule = rateLimitRule; return(this); }
private static void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule) { var log = $"{DateTime.Now:yyyy-MM-dd-HH-mm-ss}\t{identity.ClientIp}\t{identity.HttpVerb}:{identity.Path}\t{identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule {rule.Endpoint} ."; Logger.Log(log); }
/// <summary> /// async check single rule for target /// </summary> /// <param name="target"></param> /// <param name="rule"></param> /// <returns></returns> protected override async Task <RuleCheckResult> CheckSingleRuleAsync(string target, RateLimitRule rule) { var currentRule = rule as LeakyBucketRule; var amount = 1; // can not call redis TIME command in script var outflowUnit = currentRule.OutflowUnit.TotalMilliseconds; var currentTime = await _timeProvider.GetCurrentUtcMillisecondsAsync(); var startTime = AlgorithmStartTime.ToSpecifiedTypeTime(currentTime, TimeSpan.FromMilliseconds(outflowUnit), currentRule.StartTimeType); var ret = (long[]) await EvaluateScriptAsync(_leakyBucketIncrementLuaScript, new RedisKey[] { target }, new RedisValue[] { amount, currentRule.Capacity, outflowUnit, currentRule.OutflowQuantityPerUnit, currentTime, startTime, currentRule.LockSeconds }); return(new RuleCheckResult() { IsLimit = ret[0] == 0 ? false : true, Target = target, Count = ret[1], Rule = rule, Wait = ret[2], }); }