コード例 #1
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);
        }
コード例 #2
0
        /// <summary>
        /// Rate limits a request using to cache key provided
        /// </summary>
        /// <param name="cacheKey">The cache key.</param>
        /// <returns></returns>


        protected abstract Func <long> GetNumberOfRequestsAsync(
            string requestId,
            string method,
            string host,
            string routeTemplate,
            AllowedConsumptionRate policy,
            IList <RateLimitCacheKey> cacheKeys,
            ITransaction redisTransaction,
            long utcNowTicks,
            int costPerCall = 1);
コード例 #3
0
        protected static void GetDateRange(AllowedConsumptionRate allowedCallRate, DateTime dateTimeUtc, out DateTime fromUtc, out DateTime toUtc)
        {
            var periodUnits = allowedCallRate.Period.Repeating ?
                              Math.Floor(dateTimeUtc.Subtract(allowedCallRate.Period.StartDateTimeUtc).TotalHours
                                         / allowedCallRate.Period.Duration.TotalHours) : 0;

            fromUtc = periodUnits > 0 ? allowedCallRate.Period.StartDateTimeUtc.Add(
                new TimeSpan(allowedCallRate.Period.Duration.Ticks * Convert.ToInt64(periodUnits))) :
                      allowedCallRate.Period.StartDateTimeUtc;

            toUtc = fromUtc.Add(allowedCallRate.Period.Duration);
        }
コード例 #4
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());
            });
        }
コード例 #5
0
 protected long GetTicksPerUnit(AllowedConsumptionRate allowedCallRate)
 {
     return(allowedCallRate.Unit != RateLimitUnit.PerCustomPeriod ?
            (long)allowedCallRate.Unit : allowedCallRate.Period.Duration.Ticks);
 }
        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);
        }