- 添加 LocationType 常量定义替代硬编码字符串 - 优化 MatchResult、ExactMatcher、NamedMatcher 结构体字段顺序 - RadixTree.Insert 添加 locationType 参数用于调试追踪 - 更新测试代码适配新接口 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
184 lines
4.9 KiB
Go
184 lines
4.9 KiB
Go
package matcher
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"regexp"
|
||
|
||
"github.com/valyala/fasthttp"
|
||
)
|
||
|
||
// LocationEngine 统一匹配引擎
|
||
type LocationEngine struct {
|
||
prefixPriorityTree *RadixTree // ^~ 类型(优先级 2)
|
||
prefixTree *RadixTree // 普通前缀(优先级 4)
|
||
exactMatchers map[string]*ExactMatcher
|
||
namedMatchers map[string]*NamedMatcher
|
||
registeredPaths map[string]string
|
||
regexMatchers []*RegexMatcher
|
||
initialized bool
|
||
}
|
||
|
||
// NewLocationEngine 创建新引擎
|
||
func NewLocationEngine() *LocationEngine {
|
||
return &LocationEngine{
|
||
exactMatchers: make(map[string]*ExactMatcher),
|
||
prefixPriorityTree: NewRadixTree(),
|
||
prefixTree: NewRadixTree(),
|
||
regexMatchers: []*RegexMatcher{},
|
||
namedMatchers: make(map[string]*NamedMatcher),
|
||
registeredPaths: make(map[string]string),
|
||
}
|
||
}
|
||
|
||
// AddExact 添加精确匹配 location
|
||
func (e *LocationEngine) AddExact(path string, handler fasthttp.RequestHandler) error {
|
||
if e.initialized {
|
||
return errors.New("LocationEngine already initialized")
|
||
}
|
||
|
||
if err := e.checkConflict(path, "exact"); err != nil {
|
||
return err
|
||
}
|
||
|
||
matcher := NewExactMatcher(path, handler, 1)
|
||
e.exactMatchers[path] = matcher
|
||
return nil
|
||
}
|
||
|
||
// AddPrefixPriority 添加 ^~ 前缀优先匹配 location
|
||
func (e *LocationEngine) AddPrefixPriority(path string, handler fasthttp.RequestHandler) error {
|
||
if e.initialized {
|
||
return errors.New("LocationEngine already initialized")
|
||
}
|
||
|
||
if err := e.checkConflict(path, "prefix_priority"); err != nil {
|
||
return err
|
||
}
|
||
|
||
return e.prefixPriorityTree.Insert(path, handler, 2, "prefix_priority")
|
||
}
|
||
|
||
// AddRegex 添加正则匹配 location
|
||
func (e *LocationEngine) AddRegex(pattern string, handler fasthttp.RequestHandler, caseInsensitive bool) error {
|
||
if e.initialized {
|
||
return errors.New("LocationEngine already initialized")
|
||
}
|
||
|
||
matcher, err := NewRegexMatcher(pattern, handler, 3, caseInsensitive)
|
||
if err != nil {
|
||
return fmt.Errorf("invalid regex pattern: %w", err)
|
||
}
|
||
|
||
e.regexMatchers = append(e.regexMatchers, matcher)
|
||
return nil
|
||
}
|
||
|
||
// AddPrefix 添加普通前缀匹配 location
|
||
func (e *LocationEngine) AddPrefix(path string, handler fasthttp.RequestHandler) error {
|
||
if e.initialized {
|
||
return errors.New("LocationEngine already initialized")
|
||
}
|
||
|
||
if err := e.checkConflict(path, "prefix"); err != nil {
|
||
return err
|
||
}
|
||
|
||
return e.prefixTree.Insert(path, handler, 4, "prefix")
|
||
}
|
||
|
||
// AddNamed 添加命名 location
|
||
func (e *LocationEngine) AddNamed(name string, handler fasthttp.RequestHandler) error {
|
||
if e.initialized {
|
||
return errors.New("LocationEngine already initialized")
|
||
}
|
||
|
||
if existing, ok := e.namedMatchers[name]; ok {
|
||
return fmt.Errorf("named location '@%s' already registered", existing.name)
|
||
}
|
||
|
||
matcher := NewNamedMatcher(name, handler)
|
||
e.namedMatchers[name] = matcher
|
||
return nil
|
||
}
|
||
|
||
// Match 统一匹配入口
|
||
// nginx 优先级:精确匹配 → 前缀优先(^~) → 正则 → 普通前缀
|
||
func (e *LocationEngine) Match(path string) *MatchResult {
|
||
// 1. 精确匹配 (=) - O(1)
|
||
if m, ok := e.exactMatchers[path]; ok {
|
||
return m.Result()
|
||
}
|
||
|
||
// 2. 前缀优先匹配 (^~) - O(log n)
|
||
prefixPriorityResult := e.prefixPriorityTree.FindLongestPrefix(path)
|
||
if prefixPriorityResult != nil && prefixPriorityResult.Handler != nil {
|
||
return prefixPriorityResult
|
||
}
|
||
|
||
// 3. 正则匹配 (~, ~*) - 按顺序
|
||
for _, m := range e.regexMatchers {
|
||
if m.Match(path) {
|
||
result := m.Result()
|
||
result.Captures = m.GetCaptures(path)
|
||
return result
|
||
}
|
||
}
|
||
|
||
// 4. 前缀匹配(无修饰符)- O(log n)
|
||
return e.prefixTree.FindLongestPrefix(path)
|
||
}
|
||
|
||
// GetNamed 获取命名 location
|
||
func (e *LocationEngine) GetNamed(name string) *NamedMatcher {
|
||
return e.namedMatchers[name]
|
||
}
|
||
|
||
// MarkInitialized 标记初始化完成
|
||
func (e *LocationEngine) MarkInitialized() {
|
||
e.initialized = true
|
||
e.prefixPriorityTree.MarkInitialized()
|
||
e.prefixTree.MarkInitialized()
|
||
}
|
||
|
||
// checkConflict 检查路径冲突
|
||
func (e *LocationEngine) checkConflict(path, locationType string) error {
|
||
if existing, ok := e.registeredPaths[path]; ok {
|
||
return fmt.Errorf("path conflict: '%s' already registered as '%s', trying to register as '%s'",
|
||
path, existing, locationType)
|
||
}
|
||
e.registeredPaths[path] = locationType
|
||
return nil
|
||
}
|
||
|
||
// ParseRegexPattern 解析 nginx 风格的正则模式(支持 ^~ ~ ~* 前缀)
|
||
func ParseRegexPattern(pattern string) (cleanPattern string, caseInsensitive bool, isRegex bool) {
|
||
if len(pattern) == 0 {
|
||
return pattern, false, false
|
||
}
|
||
|
||
switch pattern[0] {
|
||
case '~':
|
||
cleanPattern = pattern[1:]
|
||
caseInsensitive = true
|
||
return cleanPattern, caseInsensitive, true
|
||
case '^':
|
||
if len(pattern) > 1 && pattern[1] == '~' {
|
||
cleanPattern = pattern[2:]
|
||
caseInsensitive = false
|
||
return cleanPattern, caseInsensitive, true
|
||
}
|
||
}
|
||
|
||
return pattern, false, false
|
||
}
|
||
|
||
// MustCompileRegex 编译正则表达式,失败返回原始字符串
|
||
func MustCompileRegex(pattern string) *regexp.Regexp {
|
||
re, err := regexp.Compile(pattern)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
return re
|
||
}
|