public async Task <RateLimitRecord> ProcessRequestAsync(RateLimitRecord rateRecord, RateLimitRule rule, DateTime currentTime, CancellationToken cancellationToken = default) { if (rateRecord != null) { // entry has not expired if (rateRecord.Timestamp + rule.PeriodTimespan.Value >= currentTime) { // increment request count per rule's period rateRecord.Count += 1; } else { var timeSpan = currentTime - rule.PeriodTimespan.Value; IEnumerable <RequestLogRecord> records = await _requestLogRepository.GetByClientIdAsync(rateRecord.ClientId, cancellationToken, (req) => req.Timestamp > timeSpan); rateRecord.Timestamp = records.First().Timestamp.Add(rule.PeriodTimespan.Value); rateRecord.Count = records.Count() + 1; } } else { rateRecord = new RateLimitRecord { Timestamp = DateTime.UtcNow, Count = 1 }; } //// stores: id (string) - timestamp (datetime) - total_requests (long) await _counterRepository.UpdateOrAddCounterAsync(rateRecord.ClientId, rateRecord, cancellationToken); return(rateRecord); }
public async Task <RateLimitRecord> UpdateOrAddCounterAsync(string id, RateLimitRecord entry, CancellationToken cancellationToken = default) { return(await Task <RateLimitRecord> .Factory.StartNew(() => { if (_records.ContainsKey(id)) { _records[id] = entry; return entry; } else { _records.Add(id, entry); } return entry; } )); }
// IMyScopedService is injected into Invoke public async Task InvokeAsync(HttpContext context, IRateLimitService rateLimitService) { // check if rate limiting is enabled if (_options == null) { await _next.Invoke(context); return; } // compute identity from request var identity = await ClientIdentityHelper.ResolveIdentityAsync(context, _options); var currentTime = DateTime.UtcNow; // check if user is already requested var record = await rateLimitService.GetRateLimitRecordAsync(identity); if (record == null) { record = new RateLimitRecord { ClientId = identity.ClientId, Timestamp = currentTime } } ; // check if user is blocked if (record.isBlocked) { var blockTime = record.BlockedUntillTime - currentTime; if (blockTime.TotalSeconds > 0) { await ReturnQuotaExceededResponse(context, record.BlockedUntillTime); return; } else { //reset rateCounter record if time for block is run out await rateLimitService.ResetUserAsync(identity, context.RequestAborted); } } //find all rules eligibe for request IEnumerable <RateLimitRule> rules = await rateLimitService.GetMatchingRulesAsync(identity, context.RequestAborted); foreach (var rule in rules) { RateLimitRecord rateCounter = await rateLimitService.ProcessRequestAsync(record, rule, currentTime, context.RequestAborted); if (rule.Limit > 0) { // check if limit is reached if (rateCounter.Count > rule.Limit) { var blockedUser = await rateLimitService.BlockUserByRateLimitAsync(identity, rule); // break execution await ReturnQuotaExceededResponse(context, blockedUser.BlockedUntillTime); return; } } } // Call the next delegate/middleware in the pipeline await _next(context); }