AspNetCoreRateLimit 学习
GitHub:https://github.com/stefanprodan/AspNetCoreRateLimit
Coding:https://huawu.coding.net/public/aspnetcoreratelimit/aspnetcoreratelimit/git/files
主要包含 IP 和 ClientID 两种规则
IP
优先获取请求头:X-Real-IP
其次获取:_httpContextAccessor.HttpContext.Connection.RemoteIpAddress?.ToString()
ClientID
使用配置中 ClientIdHeader 定义的请求头
如:ClientIdHeader:X-ClientId,则请求头为:X-ClientId
普通规则 GeneralRules 用法
1.当启用 EnableEndpointRateLimiting 时,会匹配 普通规则中 * 或 *:/matching_path 或 matching_verb:/matching_path 会对比一个 IsUrlMatch 方法
var limits = new List<RateLimitRule>();
//rules 为对应的IP或ClientID中定义的Rules if (rules?.Any() == true) { if (_options.EnableEndpointRateLimiting) { // search for rules with endpoints like "*" and "*:/matching_path" string path = _options.EnableRegexRuleMatching ? $".+:{identity.Path}" : $"*:{identity.Path}"; var pathLimits = rules.Where(r => path.IsUrlMatch(r.Endpoint, _options.EnableRegexRuleMatching)); limits.AddRange(pathLimits); // search for rules with endpoints like "matching_verb:/matching_path" var verbLimits = rules.Where(r => $"{identity.HttpVerb}:{identity.Path}".IsUrlMatch(r.Endpoint, _options.EnableRegexRuleMatching)); limits.AddRange(verbLimits); } else { // ignore endpoint rules and search for global rules only var genericLimits = rules.Where(r => r.Endpoint == "*"); limits.AddRange(genericLimits); } // get the most restrictive limit for each period limits = limits.GroupBy(l => l.Period).Select(l => l.OrderBy(x => x.Limit)).Select(l => l.First()).ToList(); }
2.否则 精确匹配 普通规则中 EndPoint 等于 *
IP和ClientID中的Rules
所有的通用规则和自定义规则都会生效,精确匹配的Endpoint 最好设置的规则比 *规则范围小
如果有和通用规则 Period 相等的规则,则优先使用此处的
public static bool IsUrlMatch(this string source, string value, bool useRegex)
{
if (useRegex)
{
return IsRegexMatch(source, value);
}
return source.IsWildCardMatch(value);
}
public static bool IsWildCardMatch(this string source, string value)
{
return source != null && value != null && source.ToLowerInvariant().IsMatch(value.ToLowerInvariant());
}
public static bool IsMatch(this string value, string pattern, char singleWildcard = '?', char multipleWildcard = '*')
{
int[] inputPosStack = new int[(value.Length + 1) * (pattern.Length + 1)]; // Stack containing input positions that should be tested for further matching
int[] patternPosStack = new int[inputPosStack.Length]; // Stack containing pattern positions that should be tested for further matching
int stackPos = -1; // Points to last occupied entry in stack; -1 indicates that stack is empty
bool[,] pointTested = new bool[value.Length + 1, pattern.Length + 1]; // Each true value indicates that input position vs. pattern position has been tested
int inputPos = 0; // Position in input matched up to the first multiple wildcard in pattern
int patternPos = 0; // Position in pattern matched up to the first multiple wildcard in pattern
//if (pattern == null)
// pattern = string.Empty;
// Match beginning of the string until first multiple wildcard in pattern
while (inputPos < value.Length && patternPos < pattern.Length && pattern[patternPos] != multipleWildcard && (value[inputPos] == pattern[patternPos] || pattern[patternPos] == singleWildcard))
{
inputPos++;
patternPos++;
}
// Push this position to stack if it points to end of pattern or to a general wildcard character
if (patternPos == pattern.Length || pattern[patternPos] == multipleWildcard)
{
pointTested[inputPos, patternPos] = true;
inputPosStack[++stackPos] = inputPos;
patternPosStack[stackPos] = patternPos;
}
bool matched = false;
// Repeat matching until either string is matched against the pattern or no more parts remain on stack to test
while (stackPos >= 0 && !matched)
{
inputPos = inputPosStack[stackPos]; // Pop input and pattern positions from stack
patternPos = patternPosStack[stackPos--]; // Matching will succeed if rest of the input string matches rest of the pattern
if (inputPos == value.Length && patternPos == pattern.Length)
matched = true; // Reached end of both pattern and input string, hence matching is successful
else if (patternPos == pattern.Length - 1)
matched = true; // Current pattern character is multiple wildcard and it will match all the remaining characters in the input string
else
{
// First character in next pattern block is guaranteed to be multiple wildcard
// So skip it and search for all matches in value string until next multiple wildcard character is reached in pattern
for (int curInputStart = inputPos; curInputStart < value.Length; curInputStart++)
{
int curInputPos = curInputStart;
int curPatternPos = patternPos + 1;
while (curInputPos < value.Length && curPatternPos < pattern.Length && pattern[curPatternPos] != multipleWildcard &&
(value[curInputPos] == pattern[curPatternPos] || pattern[curPatternPos] == singleWildcard))
{
curInputPos++;
curPatternPos++;
}
// If we have reached next multiple wildcard character in pattern without breaking the matching sequence, then we have another candidate for full match
// This candidate should be pushed to stack for further processing
// At the same time, pair (input position, pattern position) will be marked as tested, so that it will not be pushed to stack later again
if (((curPatternPos == pattern.Length && curInputPos == value.Length) || (curPatternPos < pattern.Length && pattern[curPatternPos] == multipleWildcard))
&& !pointTested[curInputPos, curPatternPos])
{
pointTested[curInputPos, curPatternPos] = true;
inputPosStack[++stackPos] = curInputPos;
patternPosStack[stackPos] = curPatternPos;
}
}
}
}
return matched;
}
动态修改规则
在Main 方法中添加如下代码
var host=hostBuilder.Build()
#region RateLimit 很关键,动态修改规则
using (var scope = host.Services.CreateScope())
{
// get the ClientPolicyStore instance
var clientPolicyStore = scope.ServiceProvider.GetRequiredService<IClientPolicyStore>();
// seed Client data from appsettings
_ = clientPolicyStore.SeedAsync();
// get the IpPolicyStore instance
var ipPolicyStore = scope.ServiceProvider.GetRequiredService<IIpPolicyStore>();
// seed IP data from appsettings
_ = ipPolicyStore.SeedAsync();
}
#endregion
host.Run();

浙公网安备 33010602011771号