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();