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));
 }
Beispiel #4
0
        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);
        }
Beispiel #5
0
 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)));
 }
Beispiel #8
0
        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);
        }
Beispiel #9
0
        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);
        }
Beispiel #11
0
 /// <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));
 }
Beispiel #13
0
 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
            });
        }
Beispiel #16
0
        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);
        }
Beispiel #17
0
 public override Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitRule rule, string retryAfter)
 {
     httpContext.Response.Headers.Append("Access-Control-Allow-Origin", "*");
     return(base.ReturnQuotaExceededResponse(httpContext, rule, retryAfter));
 }
Beispiel #18
0
        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}.");
 }
Beispiel #20
0
        public void RuleKeyGenIsCorrect()
        {
            var rule = new RateLimitRule(HttpVerb.Post, @"/Dummy", 1, 2, 3);

            Assert.AreEqual("post-/dummy", rule.RuleKey, "Rule key incorrect");
        }
Beispiel #21
0
        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);
        }
Beispiel #22
0
 public void NegativePerDayUnitRaisesArgumentException()
 {
     var rule = new RateLimitRule(HttpVerb.Post, @"/Dummy", 1, 2, -3);
 }
Beispiel #23
0
        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);
        }
Beispiel #24
0
 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}.");
 }
Beispiel #25
0
        /// <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);
        }
Beispiel #28
0
        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);
 }
Beispiel #31
0
        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],
            });
        }