public async void ShouldNotThrottleOnFirstCallWithAllowedCallRateOf2PerMinute()
        {
            var utcDateTime = DateTime.UtcNow;

            var clockMock = GetClockMock(utcDateTime);

            var cacheKey = new RateLimitCacheKey("testclient_01", "GET", "localhost", "/api/values",
                                                 new AllowedConsumptionRate(2, RateLimitUnit.PerMinute),
                                                 _ => RateLimitUnit.PerMinute.ToString(), clockMock.Object);

            var setup = Arrange("testclient_01", "GET", "/api/values", new AllowedConsumptionRate(2, RateLimitUnit.PerMinute),
                                utcDateTime, 1);

            var result = await setup.RedisRateLimiter.LimitRequestAsync("testclient_01",
                                                                        "GET", "localhost", "/api/values", new List <AllowedConsumptionRate>()
            {
                new AllowedConsumptionRate(2, RateLimitUnit.PerMinute)
            }, 1).ConfigureAwait(false);

            Assert.Equal(false, result.Throttled);
            Assert.Equal(0, result.WaitingIntervalInTicks);
            Assert.Equal(1, result.TokensRemaining);
            Assert.Equal(cacheKey.ToString(), result.CacheKey.ToString());

            setup.ConnectionMultiplexerMock.VerifyAll();
            setup.DatabaseMock.VerifyAll();
            setup.TransactionMock.VerifyAll();
            setup.PostViolationTransactionMock.Verify(redisTransaction =>
                                                      redisTransaction.SortedSetRangeByRankWithScoresAsync(
                                                          cacheKey.ToString(), 0, 0, It.IsAny <Order>(),
                                                          It.IsAny <CommandFlags>()), Times.Never);

            setup.clockMock.VerifyAll();
        }
コード例 #2
0
        protected override Func <long> GetNumberOfRequestsAsync(string requestId, string method, string host, string routeTemplate,
                                                                AllowedConsumptionRate allowedCallRate, IList <RateLimitCacheKey> cacheKeys,
                                                                ITransaction redisTransaction, long utcNowTicks, int costPerCall = 1)
        {
            if (costPerCall != 1)
            {
                throw new ArgumentOutOfRangeException("Only cost of value 1 is currently supported by the sliding time window rate limiter");
            }

            if (allowedCallRate.MaxBurst != 0)
            {
                throw new NotSupportedException("MaxBurst is not supported by the sliding time window rate limiter. Please consider using the leaky bucket rate limiter.");
            }

            var cacheKey =
                new RateLimitCacheKey(requestId, method, host, routeTemplate, allowedCallRate,
                                      RateLimitTypeCacheKeyFormatMapping[allowedCallRate.Unit].Invoke(allowedCallRate));

            var cacheKeyString = cacheKey.ToString();

            cacheKeys.Add(cacheKey);

            var sortedSetRemoveRangeByScoreAsync = redisTransaction.SortedSetRemoveRangeByScoreAsync(
                cacheKeyString, 0, utcNowTicks - GetTicksPerUnit(cacheKey.AllowedConsumptionRate));

            var sortedSetAddAsync = redisTransaction.SortedSetAddAsync(cacheKeyString, Guid.NewGuid().ToString(), utcNowTicks);
            var numberOfRequestsInWindowAsyncList = redisTransaction.SortedSetLengthAsync(cacheKeyString);
            var expireTask = redisTransaction.KeyExpireAsync(cacheKeyString,
                                                             cacheKey.Expiration.Add(new TimeSpan(0, 1, 0)));

            return(() => numberOfRequestsInWindowAsyncList.Result);
        }
コード例 #3
0
 private long GetWaitingIntervalInTicks(Func <long> getOldestRequestTimestampInTicksFunc,
                                        RateLimitCacheKey violatedCacheKey,
                                        long utcNowTicks)
 {
     return(getOldestRequestTimestampInTicksFunc() +
            GetTicksPerUnit(violatedCacheKey.AllowedConsumptionRate) - utcNowTicks);
 }
        public async void ShouldThrottleOnThirdCallWithAllowedCallRateOf2PerMinute()
        {
            var utcDateTime = DateTime.UtcNow;

            var clockMock = GetClockMock(utcDateTime);

            var cacheKey = new RateLimitCacheKey("testclient_01", "GET", "localhost", "/api/values",
                                                 new AllowedConsumptionRate(2, RateLimitUnit.PerMinute),
                                                 _ => RateLimitUnit.PerMinute.ToString(), clockMock.Object);

            var setup = Arrange("testclient_01", "GET", "/api/values", new AllowedConsumptionRate(2, RateLimitUnit.PerMinute),
                                utcDateTime, 3);

            var result = await setup.RedisRateLimiter.LimitRequestAsync("testclient_01",
                                                                        "GET", "localhost", "/api/values", new List <AllowedConsumptionRate>()
            {
                new AllowedConsumptionRate(2, RateLimitUnit.PerMinute)
            }).ConfigureAwait(false);

            Assert.Equal(result.State, ResultState.Throttled);
            Assert.Equal(cacheKey.ToString(), result.CacheKey.ToString());

            setup.ConnectionMultiplexerMock.VerifyAll();
            setup.DatabaseMock.VerifyAll();
            setup.TransactionMock.VerifyAll();
            setup.PostViolationTransactionMock.VerifyAll();
            setup.clockMock.VerifyAll();
        }
コード例 #5
0
 public Task <RateLimitingResult> LimitRequestAsync(RateLimitCacheKey cacheKey)
 {
     return(LimitRequestAsync(cacheKey.RequestId, cacheKey.Method, cacheKey.Host, cacheKey.RouteTemplate,
                              new List <AllowedConsumptionRate>()
     {
         new AllowedConsumptionRate(cacheKey.Limit, cacheKey.Unit)
     }, 1));
 }
コード例 #6
0
 /// <summary>
 ///
 /// </summary>
 /// <param name="cacheKey"></param>
 /// <param name="throttled"></param>
 /// <param name="waitingIntervalInTicks"></param>
 /// <param name="callsRemaining"></param>
 /// <param name="violatedPolicyName"></param>
 public RateLimitingResult(ResultState state, long waitingIntervalInTicks, RateLimitCacheKey cacheKey, int callsRemaining, string violatedPolicyName = "")
 {
     State = state;
     WaitingIntervalInTicks = waitingIntervalInTicks;
     CacheKey           = cacheKey;
     TokensRemaining    = callsRemaining;
     ViolatedPolicyName = violatedPolicyName;
 }
コード例 #7
0
        protected override Func <long> GetOldestRequestTimestampInTicksFunc(ITransaction postViolationTransaction,
                                                                            RateLimitCacheKey cacheKey, long utcNowTicks)
        {
            var task = postViolationTransaction.SortedSetRangeByRankWithScoresAsync(cacheKey.ToString(), 0, 0);

            return(() =>
            {
                var sortedSetEntries = task.Result;

                if (sortedSetEntries == null || sortedSetEntries.Length == 0)
                {
                    return utcNowTicks - GetTicksPerUnit(cacheKey.AllowedConsumptionRate);
                }

                return (long)sortedSetEntries[0].Score;
            });
        }
コード例 #8
0
        protected override Func <long> GetNumberOfRequestsAsync(
            string requestId,
            string method,
            string host,
            string routeTemplate,
            AllowedConsumptionRate allowedConsumptionRate,
            IList <RateLimitCacheKey> cacheKeys,
            ITransaction redisTransaction,
            long utcNowTicks,
            int costPerCall = 1)
        {
            RateLimitCacheKey cacheKey =
                new RateLimitCacheKey(requestId, method, host, routeTemplate, allowedConsumptionRate,
                                      RateLimitTypeCacheKeyFormatMapping[allowedConsumptionRate.Unit].Invoke(allowedConsumptionRate));

            var cacheKeyString = cacheKey.ToString();

            cacheKeys.Add(cacheKey);

            var ttlInSeconds = allowedConsumptionRate.MaxBurst / allowedConsumptionRate.Limit * (long)allowedConsumptionRate.Unit / TimeSpan.TicksPerSecond + 120;

            var scriptResultTask = redisTransaction.ScriptEvaluateAsync(_loadedLuaScript,
                                                                        new
            {
                key         = (RedisKey)cacheKeyString,
                utcNowTicks = utcNowTicks,
                leakageAmountPerInterval = allowedConsumptionRate.Limit,
                leakageIntervalInTicks   = GetTicksPerUnit(allowedConsumptionRate),
                costPerCall = costPerCall,
                ttl         = ttlInSeconds
            });

            return(() =>
            {
                return Convert.ToInt64(scriptResultTask.Result.ToString());
            });
        }
コード例 #9
0
        protected override void UndoUnsuccessfulRequestCount(ITransaction postViolationTransaction, RateLimitCacheKey cacheKey, long now, int costPerCall = 1)
        {
            if (costPerCall != 1)
            {
                throw new NotSupportedException("Only cost of value 1 is currently supported by the sliding window rate limiter");
            }

            var adjust = postViolationTransaction.SortedSetRemoveRangeByRankAsync(cacheKey.ToString(), -1, -1);
        }
コード例 #10
0
 protected abstract Func <long> GetOldestRequestTimestampInTicksFunc(
     ITransaction postViolationTransaction, RateLimitCacheKey cacheKey,
     long utcNowTicks);
コード例 #11
0
 protected abstract void UndoUnsuccessfulRequestCount(
     ITransaction postViolationTransaction,
     RateLimitCacheKey cacheKey,
     long utcNowTicks, int costPerCall = 1);
コード例 #12
0
 protected override void UndoUnsuccessfulRequestCount(ITransaction postViolationTransaction, RateLimitCacheKey cacheKey, long utcNowTicks, int costPerCall = 1)
 {
     postViolationTransaction.HashDecrementAsync(cacheKey.ToString(), "t", costPerCall);
 }
コード例 #13
0
        protected override Func <long> GetOldestRequestTimestampInTicksFunc(ITransaction postViolationTransaction, RateLimitCacheKey cacheKey, long utcNowTicks)
        {
            var task = postViolationTransaction.HashGetAsync(cacheKey.ToString(), "lu");

            return(() =>
            {
                task.Result.TryParse(out double value);
                return (long)value;
            });
        }
        Arrange(string requestId, string method, string routeTemplate,
                AllowedConsumptionRate allowedCallRate,
                DateTime utcDateTime, long numberOfRequestsMadeSoFar)
        {
            var clockMock = GetClockMock(utcDateTime);

            var cacheKey = new RateLimitCacheKey(requestId, method, "localhost", routeTemplate,
                                                 allowedCallRate,
                                                 _ => allowedCallRate.Unit.ToString(), clockMock.Object);

            var connectionMultiplexerMock = new Mock <IConnectionMultiplexer>(MockBehavior.Strict);

            var dbMock = new Mock <IDatabase>(MockBehavior.Strict);

            var transactionMock = new Mock <ITransaction>(MockBehavior.Strict);

            transactionMock.Setup(redisTransaction =>
                                  redisTransaction.SortedSetRemoveRangeByScoreAsync(
                                      cacheKey.ToString(), 0,
                                      utcDateTime.Ticks - (long)cacheKey.Unit, It.IsAny <Exclude>(),
                                      It.IsAny <CommandFlags>())).Returns(Task.FromResult(10L));

            transactionMock.Setup(redisTransaction =>
                                  redisTransaction.SortedSetAddAsync(
                                      cacheKey.ToString(), It.IsAny <RedisValue>(), utcDateTime.Ticks, It.IsAny <When>(),
                                      It.IsAny <CommandFlags>())).Returns(Task.FromResult(true));

            transactionMock.Setup(redisTransaction =>
                                  redisTransaction.SortedSetLengthAsync(
                                      cacheKey.ToString(), It.IsAny <double>(), It.IsAny <double>(), It.IsAny <Exclude>(),
                                      It.IsAny <CommandFlags>())).Returns(
                Task.FromResult(numberOfRequestsMadeSoFar));

            transactionMock.Setup(redisTransaction =>
                                  redisTransaction.KeyExpireAsync(
                                      cacheKey.ToString(), cacheKey.Expiration.Add(new TimeSpan(0, 1, 0)), It.IsAny <CommandFlags>())).Returns(Task.FromResult(true));

            transactionMock.Setup(redisTransaction =>
                                  redisTransaction.ExecuteAsync(CommandFlags.None)).Returns(Task.FromResult(true));

            var postViolationTransactionMock = new Mock <ITransaction>(MockBehavior.Strict);

            postViolationTransactionMock.Setup(redisTransaction =>
                                               redisTransaction.SortedSetRangeByRankWithScoresAsync(
                                                   cacheKey.ToString(), 0, 0, It.IsAny <Order>(),
                                                   It.IsAny <CommandFlags>()))
            .Returns(Task.FromResult(new SortedSetEntry[] { new SortedSetEntry(It.IsAny <RedisValue>(), utcDateTime.Ticks) }));

            postViolationTransactionMock.Setup(redisTransaction =>
                                               redisTransaction.ExecuteAsync(CommandFlags.None)).Returns(Task.FromResult(true));

            dbMock.SetupSequence(db => db.CreateTransaction(null))
            .Returns(transactionMock.Object)
            .Returns(postViolationTransactionMock.Object);

            connectionMultiplexerMock.Setup(connection => connection.IsConnected).Returns(true);
            connectionMultiplexerMock.Setup(connection => connection.GetDatabase(-1, null)).Returns(dbMock.Object);

            var rateLimiter = new SlidingTimeWindowRateLimiter("http://localhost",
                                                               clock: clockMock.Object,
                                                               connectToRedisFunc: async() => await Task.FromResult(connectionMultiplexerMock.Object),
                                                               countThrottledRequests: true);

            return(rateLimiter, connectionMultiplexerMock, dbMock,
                   transactionMock, postViolationTransactionMock, clockMock);
        }