lolly/internal/matcher/location.go
xfy 53eaec57ad feat(matcher): 添加 nginx 风格 location 匹配引擎
实现 nginx 兼容的 location 匹配系统,支持:
- 精确匹配 (=) - Hash Map O(1)
- 前缀优先匹配 (^~) - Radix Tree
- 正则匹配 (~, ~*) - 按配置顺序
- 普通前缀匹配 - Radix Tree 最长匹配
- 命名 location (@name) - 内部重定向

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:26:22 +08:00

195 lines
5.1 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 {
// 精确匹配 - Hash Map (O(1))
exactMatchers map[string]*ExactMatcher
// 前缀匹配 - Radix Tree (O(log n))
prefixPriorityTree *RadixTree // ^~ 类型(优先级 2
prefixTree *RadixTree // 普通前缀(优先级 4
// 正则匹配 - Linear Scan按配置顺序
regexMatchers []*RegexMatcher
// 命名 location - Hash Map
namedMatchers map[string]*NamedMatcher
// 初始化标记
initialized bool
// 冲突检测
registeredPaths map[string]string
}
// 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)
}
// 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)
}
// 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
}