public RateLimitCounter GetOrCreate(string clientId) { try { var strResults = _memoryCache.GetString(clientId); if (strResults == null) { var rateLimitCounter = new RateLimitCounter(); _memoryCache.SetString(clientId, JsonConvert.SerializeObject(rateLimitCounter), _distributedCacheEntryOptions); return(rateLimitCounter); } else { var ratelimit = JsonConvert.DeserializeObject <RateLimitCounter>(strResults); ratelimit.Increment(); return(ratelimit); } } catch (Exception ex) { _logger.LogError($"Exception when Set ClientId: {clientId}. Exception: {ex}"); return(null); } }
public bool Set(string id, RateLimitCounter counter, TimeSpan expirationTime) { var key = GetKeyPrefix(id); var expiry = (int)expirationTime.TotalSeconds; return(_client.Set(key, counter, expiry)); }
private RateLimitCounter GetCounter(string counterKey, RateLimitRule rule) { var counter = new RateLimitCounter { Timestamp = DateTime.UtcNow, Count = 1 }; var entry = _rateLimitCounterHandler.Get(counterKey); if (entry.HasValue) { // entry has not expired if (entry.Value.Timestamp + rule.PeriodTimespan.Value >= DateTime.UtcNow) { // increment request count var totalRequests = entry.Value.Count + 1; // deep copy counter = new RateLimitCounter { Timestamp = entry.Value.Timestamp, Count = totalRequests }; } } return(counter); }
public Task <RateLimitResult> AddRequestAsync(string id, RateLimitRule rule) { var counter = Get(id); if (counter == null || counter.IsExpired) { // serial create of new rate limit counter lock (_lock) { counter = Get(id); if (counter == null || counter.IsExpired) { counter = new RateLimitCounter(rule.UseSlidingExpiration, rule.PeriodTimeSpan); Set(id, counter, rule.PeriodTimeSpan, rule.UseSlidingExpiration); return(Task.FromResult(new RateLimitResult { Success = true, Remaining = rule.Limit - 1, Expiry = DateTime.UtcNow.Add(rule.PeriodTimeSpan) })); } } } return(Task.FromResult(counter.AddRequest(rule.Limit))); }
/// <summary> /// 处理请求数 /// </summary> /// <param name="requestIdentity"></param> /// <param name="rule"></param> /// <returns></returns> private RateLimitCounter ProcessRequest(RequestIdentity requestIdentity, RateLimitRule rule) { var counter = new RateLimitCounter { Timestamp = DateTime.Now, TotalRequests = 1 }; var counterId = ComputeCounterKey(requestIdentity, rule); lock (_processLocker) { var entry = _counterStore.Get(counterId); if (entry != null) { // 没有过期,请求数+1 if (entry.Timestamp + rule.PeriodTimespan.Value >= DateTime.Now) { var totalRequests = entry.TotalRequests + 1; counter = new RateLimitCounter { Timestamp = entry.Timestamp, TotalRequests = totalRequests }; } } _counterStore.Set(counterId, counter, rule.PeriodTimespan.Value); } return(counter); }
public virtual void LogBlockedRequest( HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule) { this._logger.LogInformation(string.Format("Request {0}:{1} from IP {2} has been blocked, quota {3}/{4} exceeded by {5}. Blocked by rule {6}, TraceIdentifier {7}.", (object)identity.HttpVerb, (object)identity.Path, (object)identity.ClientIp, (object)rule.Limit, (object)rule.Period, (object)counter.TotalRequests, (object)rule.Endpoint, (object)httpContext.TraceIdentifier)); }
public void Set(string id, RateLimitCounter counter, TimeSpan expirationTime) { var options = new MemoryCacheEntryOptions { Priority = CacheItemPriority.NeverRemove }; options.SetAbsoluteExpiration(expirationTime); _cache.Set(id, counter, options); }
private void Set(string id, RateLimitCounter counter, TimeSpan expirationPeriod, bool slidingExpiration) { var options = new MemoryCacheEntryOptions(); if (slidingExpiration) { options.SetSlidingExpiration(expirationPeriod); } else { options.SetAbsoluteExpiration(expirationPeriod); } _memoryCache.Set(id, counter, options); }
private async Task SetAsync(string id, RateLimitCounter counter, TimeSpan expirationPeriod, bool slidingExpiration) { var options = new DistributedCacheEntryOptions(); if (slidingExpiration) { options.SetSlidingExpiration(expirationPeriod); } else { options.SetAbsoluteExpiration(expirationPeriod); } await _memoryCache.SetStringAsync(id, JsonConvert.SerializeObject(counter), options); }
public async Task Invoke(HttpContext httpContext) { MyIPRateMiddleware rateLimitMiddleware = this; if (rateLimitMiddleware._options == null) { await rateLimitMiddleware._next(httpContext); } else { ClientRequestIdentity identity = rateLimitMiddleware.SetIdentity(httpContext); if (rateLimitMiddleware._processor.IsWhitelisted(identity)) { await rateLimitMiddleware._next(httpContext); } else { List <RateLimitRule> rules = rateLimitMiddleware._processor.GetMatchingRules(identity); foreach (RateLimitRule rule in rules) { if (rule.Limit > 0L) { RateLimitCounter counter = rateLimitMiddleware._processor.ProcessRequest(identity, rule); if (!(counter.Timestamp + rule.PeriodTimespan.Value < DateTime.UtcNow) && counter.TotalRequests > rule.Limit) { string retryAfter = rateLimitMiddleware._processor.RetryAfterFrom(counter.Timestamp, rule); rateLimitMiddleware.LogBlockedRequest(httpContext, identity, counter, rule); await rateLimitMiddleware.ReturnQuotaExceededResponse(httpContext, rule, retryAfter); return; } } } if (rules.Any <RateLimitRule>() && !rateLimitMiddleware._options.DisableRateLimitHeaders) { RateLimitRule rule = rules.OrderByDescending <RateLimitRule, TimeSpan>((Func <RateLimitRule, TimeSpan>)(x => x.PeriodTimespan.Value)).First <RateLimitRule>(); RateLimitHeaders rateLimitHeaders = rateLimitMiddleware._processor.GetRateLimitHeaders(identity, rule); rateLimitHeaders.Context = httpContext; httpContext.Response.OnStarting(new Func <object, Task>(rateLimitMiddleware.SetRateLimitHeaders), (object)rateLimitHeaders); } await rateLimitMiddleware._next(httpContext); } } }
public override void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule) { base.LogBlockedRequest(httpContext, identity, counter, rule); var key = $"blockedIp_{identity.ClientIp}"; _memoryCache.TryGetValue(key, out int blockedCount); blockedCount++; if (blockedCount > 10) { _blockIpService.BlockIpAsync(identity.ClientIp, false); _logger.LogInformation($"Blocked {identity.ClientIp}"); } else { _memoryCache.Set(key, blockedCount, new MemoryCacheEntryOptions().SetSlidingExpiration(new System.TimeSpan(0, 5, 0))); } }
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 RateLimitCounter GetOrCreate(string clientId) { try { return(_memoryCache.GetOrCreate(clientId, entry => { //common issue with getOrCreate of IMemoryCache force us to use lock lock (_locker) { var rateLimitCounter = new RateLimitCounter(); entry.SetValue(rateLimitCounter); entry.SetAbsoluteExpiration(_options.Value.ExpirationTime); return rateLimitCounter; } })); } catch (Exception ex) { _logger.LogError($"Exception when Set ClientId: {clientId}. Exception: {ex}"); return(null); } }
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 override void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule) { base.LogBlockedRequest(httpContext, identity, counter, rule); var key = $"blockedIp_{identity.ClientIp}"; _memoryCache.TryGetValue(key, out int blockedCount); blockedCount++; if (blockedCount > 10) { _blockIpService.BlockIpAsync(identity.ClientIp, false); _logger.LogInformation(Constants.BypassFiltersEventId, null, "Banned {0}. \nInfo: \n{1}", identity.ClientIp, GetRequestInfo(httpContext)); } else { _logger.LogInformation(Constants.BypassFiltersEventId, null, "Request blocked {0}. \nInfo: \n{1}", identity.ClientIp, GetRequestInfo(httpContext)); _memoryCache.Set(key, blockedCount, new MemoryCacheEntryOptions().SetSlidingExpiration(new TimeSpan(0, 5, 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); }
public async Task <RateLimitResult> AddRequestAsync(string id, RateLimitRule rule) { // TODO: REQUIRES REVIEW // RateLimitCounter should not be serialised to distributed cache. // Directly store underlying data to facilitate atomic distributed updates. // REDIS example: http://tech.domain.com.au/2017/11/protect-your-api-resources-with-rate-limiting/ var counter = await GetAsync(id); if (counter == null) { counter = new RateLimitCounter(rule.UseSlidingExpiration, rule.PeriodTimeSpan); await SetAsync(id, counter, rule.PeriodTimeSpan, rule.UseSlidingExpiration); return(new RateLimitResult { Success = true, Remaining = rule.Limit - 1, Expiry = DateTime.UtcNow.Add(rule.PeriodTimeSpan) }); } return(counter.AddRequest(rule.Limit)); }
public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule) { _logger.LogDebug($"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { DownstreamRoute.ReRoute.UpstreamPathTemplate }, TraceIdentifier {httpContext.TraceIdentifier}."); }
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); }
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 ."); }
public void Set(string id, RateLimitCounter counter, TimeSpan expirationTime) { _memoryCache.SetOrReset(id, counter, expirationTime); }
protected override void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule) { _logger.LogInformation($"Request {identity.HttpVerb}:{identity.Path} from IP {identity.ClientIp} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.Count - rule.Limit}. Blocked by rule {rule.Endpoint}, TraceIdentifier {httpContext.TraceIdentifier}. MonitorMode: {rule.MonitorMode}"); }
public override void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule) { // custom logging for request blocking this.logger.LogWarning($" {DateTime.Now.ToString()} : User with id {identity.ClientId} was blocked to access {identity.HttpVerb} : {identity.Path} due to [ Limit : {rule.Limit} , Period : {rule.Period} , Endpoint : {rule.Endpoint} ]! "); base.LogBlockedRequest(httpContext, identity, counter, rule); }
public async Task Invoke(HttpContext context) { if (_options == null) { await _next(context); return; } ClientRequestIdentity identity = await ResolveIdentityAsync(context); TProcessor processor = _processor; if (processor.IsWhitelisted(identity)) { await _next(context); return; } processor = _processor; IEnumerable <RateLimitRule> rules = await processor.GetMatchingRulesAsync(identity, context.RequestAborted); Dictionary <RateLimitRule, RateLimitCounter> rulesDict = new Dictionary <RateLimitRule, RateLimitCounter>(); foreach (RateLimitRule rule2 in rules) { processor = _processor; RateLimitCounter rateLimitCounter = await processor.ProcessRequestAsync(identity, rule2, context.RequestAborted); if (rule2.Limit > 0.0) { if (rateLimitCounter.Timestamp + rule2.PeriodTimespan.Value < DateTime.UtcNow) { continue; } if (rateLimitCounter.Count > rule2.Limit) { string retryAfter = rateLimitCounter.Timestamp.RetryAfterFrom(rule2); LogBlockedRequest(context, identity, rateLimitCounter, rule2); if (_options.RequestBlockedBehaviorAsync != null) { await _options.RequestBlockedBehaviorAsync(context, identity, rateLimitCounter, rule2); } if (!rule2.MonitorMode) { await ReturnQuotaExceededResponse(context, rule2, retryAfter); return; } } } else { LogBlockedRequest(context, identity, rateLimitCounter, rule2); if (_options.RequestBlockedBehaviorAsync != null) { await _options.RequestBlockedBehaviorAsync(context, identity, rateLimitCounter, rule2); } if (!rule2.MonitorMode) { await ReturnQuotaExceededResponse(context, rule2, int.MaxValue.ToString(CultureInfo.InvariantCulture)); return; } } rulesDict.Add(rule2, rateLimitCounter); } if (rulesDict.Any() && !_options.DisableRateLimitHeaders) { KeyValuePair <RateLimitRule, RateLimitCounter> rule = rulesDict.OrderByDescending((KeyValuePair <RateLimitRule, RateLimitCounter> x) => x.Key.PeriodTimespan).FirstOrDefault(); processor = _processor; RateLimitHeaders headers = processor.GetRateLimitHeaders(rule.Value, rule.Key, context.RequestAborted); headers.Context = context; context.Response.OnStarting(new Func <object, Task>(SetRateLimitHeaders), headers); } await _next(context); }
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); }
public bool Set(string id, RateLimitCounter counter, TimeSpan expirationTime) { _client.Set(id, counter, expirationTime); return(true); }