lolly/internal/matcher/location.go
xfy ca03c121d3 refactor(matcher): 提取 LocationType 常量并优化结构体字段布局
- 添加 LocationType 常量定义替代硬编码字符串
- 优化 MatchResult、ExactMatcher、NamedMatcher 结构体字段顺序
- RadixTree.Insert 添加 locationType 参数用于调试追踪
- 更新测试代码适配新接口

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 10:11:45 +08:00

184 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}