diff --git a/src/AspNetCoreRateLimit/Middleware/ClientRateLimitMiddleware.cs b/src/AspNetCoreRateLimit/Middleware/ClientRateLimitMiddleware.cs index 5cb06e09..b2248bbe 100644 --- a/src/AspNetCoreRateLimit/Middleware/ClientRateLimitMiddleware.cs +++ b/src/AspNetCoreRateLimit/Middleware/ClientRateLimitMiddleware.cs @@ -14,7 +14,7 @@ public ClientRateLimitMiddleware(RequestDelegate next, IClientPolicyStore policyStore, IRateLimitConfiguration config, ILogger logger) - : base(next, options?.Value, new ClientRateLimitProcessor(options?.Value, policyStore, processingStrategy), config) + : base(next, options?.Value, new ClientRateLimitProcessor(options?.Value, policyStore, processingStrategy), config, logger) { _logger = logger; } diff --git a/src/AspNetCoreRateLimit/Middleware/IpRateLimitMiddleware.cs b/src/AspNetCoreRateLimit/Middleware/IpRateLimitMiddleware.cs index 71834d6c..ec1c17df 100644 --- a/src/AspNetCoreRateLimit/Middleware/IpRateLimitMiddleware.cs +++ b/src/AspNetCoreRateLimit/Middleware/IpRateLimitMiddleware.cs @@ -15,7 +15,7 @@ public IpRateLimitMiddleware(RequestDelegate next, IRateLimitConfiguration config, ILogger logger ) - : base(next, options?.Value, new IpRateLimitProcessor(options?.Value, policyStore, processingStrategy), config) + : base(next, options?.Value, new IpRateLimitProcessor(options?.Value, policyStore, processingStrategy), config, logger) { _logger = logger; } diff --git a/src/AspNetCoreRateLimit/Middleware/RateLimitMiddleware.cs b/src/AspNetCoreRateLimit/Middleware/RateLimitMiddleware.cs index 3b6f63fc..208cf459 100644 --- a/src/AspNetCoreRateLimit/Middleware/RateLimitMiddleware.cs +++ b/src/AspNetCoreRateLimit/Middleware/RateLimitMiddleware.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; namespace AspNetCoreRateLimit { @@ -14,17 +16,20 @@ public abstract class RateLimitMiddleware private readonly TProcessor _processor; private readonly RateLimitOptions _options; private readonly IRateLimitConfiguration _config; + private readonly ILogger> _logger; protected RateLimitMiddleware( RequestDelegate next, RateLimitOptions options, TProcessor processor, - IRateLimitConfiguration config) + IRateLimitConfiguration config, + ILogger> logger) { _next = next; _options = options; _processor = processor; _config = config; + _logger = logger; _config.RegisterResolvers(); } @@ -47,29 +52,51 @@ public async Task Invoke(HttpContext context) return; } - var rules = await _processor.GetMatchingRulesAsync(identity, context.RequestAborted); - - var rulesDict = new Dictionary(); - - foreach (var rule in rules) + try { - // increment counter - var rateLimitCounter = await _processor.ProcessRequestAsync(identity, rule, context.RequestAborted); + var rules = await _processor.GetMatchingRulesAsync(identity, context.RequestAborted); - if (rule.Limit > 0) + var rulesDict = new Dictionary(); + + foreach (var rule in rules) { - // check if key expired - if (rateLimitCounter.Timestamp + rule.PeriodTimespan.Value < DateTime.UtcNow) - { - continue; - } + // increment counter + var rateLimitCounter = await _processor.ProcessRequestAsync(identity, rule, context.RequestAborted); - // check if limit is reached - if (rateLimitCounter.Count > rule.Limit) + if (rule.Limit > 0) { - //compute retry after value - var retryAfter = rateLimitCounter.Timestamp.RetryAfterFrom(rule); + // check if key expired + if (rateLimitCounter.Timestamp + rule.PeriodTimespan.Value < DateTime.UtcNow) + { + continue; + } + // check if limit is reached + if (rateLimitCounter.Count > rule.Limit) + { + //compute retry after value + var retryAfter = rateLimitCounter.Timestamp.RetryAfterFrom(rule); + + // log blocked request + LogBlockedRequest(context, identity, rateLimitCounter, rule); + + if (_options.RequestBlockedBehaviorAsync != null) + { + await _options.RequestBlockedBehaviorAsync(context, identity, rateLimitCounter, rule); + } + + if (!rule.MonitorMode) + { + // break execution + await ReturnQuotaExceededResponse(context, rule, retryAfter); + + return; + } + } + } + // if limit is zero or less, block the request. + else + { // log blocked request LogBlockedRequest(context, identity, rateLimitCounter, rule); @@ -80,45 +107,34 @@ public async Task Invoke(HttpContext context) if (!rule.MonitorMode) { - // break execution - await ReturnQuotaExceededResponse(context, rule, retryAfter); + // break execution (Int32 max used to represent infinity) + await ReturnQuotaExceededResponse(context, rule, + int.MaxValue.ToString(CultureInfo.InvariantCulture)); return; } } + + rulesDict.Add(rule, rateLimitCounter); } - // if limit is zero or less, block the request. - else - { - // log blocked request - LogBlockedRequest(context, identity, rateLimitCounter, rule); - if (_options.RequestBlockedBehaviorAsync != null) - { - await _options.RequestBlockedBehaviorAsync(context, identity, rateLimitCounter, rule); - } + // set X-Rate-Limit headers for the longest period + if (rulesDict.Any() && !_options.DisableRateLimitHeaders) + { + var rule = rulesDict.OrderByDescending(x => x.Key.PeriodTimespan).FirstOrDefault(); + var headers = _processor.GetRateLimitHeaders(rule.Value, rule.Key, context.RequestAborted); - if (!rule.MonitorMode) - { - // break execution (Int32 max used to represent infinity) - await ReturnQuotaExceededResponse(context, rule, int.MaxValue.ToString(System.Globalization.CultureInfo.InvariantCulture)); + headers.Context = context; - return; - } + context.Response.OnStarting(SetRateLimitHeaders, state: headers); } - - rulesDict.Add(rule, rateLimitCounter); } - - // set X-Rate-Limit headers for the longest period - if (rulesDict.Any() && !_options.DisableRateLimitHeaders) + catch (Exception e) { - var rule = rulesDict.OrderByDescending(x => x.Key.PeriodTimespan).FirstOrDefault(); - var headers = _processor.GetRateLimitHeaders(rule.Value, rule.Key, context.RequestAborted); - - headers.Context = context; - - context.Response.OnStarting(SetRateLimitHeaders, state: headers); + if (_options.DoNotInterruptRequestPipelineOnFailure) + _logger.LogError(e, "An error occured while processing the rate limit"); + else + throw; } await _next.Invoke(context); diff --git a/src/AspNetCoreRateLimit/Models/RateLimitOptions.cs b/src/AspNetCoreRateLimit/Models/RateLimitOptions.cs index 89d2cf73..ab19f3fc 100644 --- a/src/AspNetCoreRateLimit/Models/RateLimitOptions.cs +++ b/src/AspNetCoreRateLimit/Models/RateLimitOptions.cs @@ -71,5 +71,10 @@ public class RateLimitOptions /// Gets or sets behavior after the request is blocked /// public Func RequestBlockedBehaviorAsync { get; set; } + + /// + /// Gets or sets the behavior that determines whether the request pipeline should be aborted in case of any rate limiting issues (i.e. Redis or SQLServer is not available when used as a distributed counter store). + /// + public bool DoNotInterruptRequestPipelineOnFailure { get; set; } } } \ No newline at end of file