lolly/internal/matcher/location.go
xfy 6094327620 docs(matcher): 为 location 匹配引擎添加标准化 godoc 注释
为 matcher 包所有文件添加完整文档注释:
- conflict: 路径冲突检测器
- exact: 精确路径匹配器(O(1) hash map)
- location: 统一匹配引擎(整合所有策略)
- matcher: 匹配结果和接口定义
- named: 命名捕获组匹配器
- prefix: 普通前缀匹配器
- prefix_priority: 前缀优先匹配器(^~)
- radix: Radix Tree 最长前缀匹配
- regex: 正则表达式匹配器

注释说明匹配优先级顺序(精确 > 前缀优先 > 正则 > 普通前缀),
以及各匹配器的使用方法和性能特点。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 10:59:27 +08:00

305 lines
8.2 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 提供 nginx 风格的 location 匹配引擎实现。
//
// 该文件实现统一的 LocationEngine整合所有匹配策略
// 按照 nginx 优先级顺序执行匹配。
//
// 匹配优先级从高到低:
// 1. 精确匹配(=
// 2. 前缀优先匹配(^~
// 3. 正则匹配(~, ~*
// 4. 普通前缀匹配
//
// 作者xfy
package matcher
import (
"errors"
"fmt"
"regexp"
"github.com/valyala/fasthttp"
)
// LocationEngine 统一匹配引擎。
//
// 整合所有 location 匹配策略,按照 nginx 优先级顺序执行:
// - 精确匹配O(1) hash map 查找
// - 前缀优先Radix Tree 最长前缀匹配
// - 正则匹配:按注册顺序逐个尝试
// - 普通前缀Radix Tree 最长前缀匹配
//
// 注意事项:
// - 调用 MarkInitialized 后不可再添加 location
// - 正则匹配器按注册顺序执行,配置时应将高频模式放在前面
type LocationEngine struct {
// prefixPriorityTree ^~ 类型前缀优先匹配树(优先级 2
prefixPriorityTree *RadixTree
// prefixTree 普通前缀匹配树(优先级 4
prefixTree *RadixTree
// exactMatchers 精确匹配映射
exactMatchers map[string]*ExactMatcher
// namedMatchers 命名 location 映射
namedMatchers map[string]*NamedMatcher
// registeredPaths 已注册路径(用于冲突检测)
registeredPaths map[string]string
// regexMatchers 正则匹配器列表(按注册顺序)
regexMatchers []*RegexMatcher
// initialized 是否已完成初始化
initialized bool
}
// NewLocationEngine 创建新的匹配引擎实例。
//
// 返回值:
// - *LocationEngine: 初始化的引擎实例
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。
//
// 参数:
// - path: 精确匹配路径
// - handler: 请求处理器
//
// 返回值:
// - error: 引擎已初始化或路径冲突时返回错误
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。
//
// 参数:
// - path: 前缀优先路径
// - handler: 请求处理器
//
// 返回值:
// - error: 引擎已初始化或路径冲突时返回错误
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。
//
// 参数:
// - pattern: 正则表达式模式
// - handler: 请求处理器
// - caseInsensitive: 是否大小写不敏感(~* 模式)
//
// 返回值:
// - error: 引擎已初始化或正则表达式无效时返回错误
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。
//
// 参数:
// - path: 前缀路径
// - handler: 请求处理器
//
// 返回值:
// - error: 引擎已初始化或路径冲突时返回错误
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。
//
// 命名 location 用于内部跳转(如 error_page不直接匹配请求路径。
//
// 参数:
// - name: location 名称(不含 @ 前缀)
// - handler: 请求处理器
//
// 返回值:
// - error: 引擎已初始化或名称重复时返回错误
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 优先级顺序执行匹配:
// 1. 精确匹配(=- O(1)
// 2. 前缀优先匹配(^~- O(log n)
// 3. 正则匹配(~, ~*- 按顺序
// 4. 普通前缀匹配 - O(log n)
//
// 参数:
// - path: 请求路径
//
// 返回值:
// - *MatchResult: 匹配结果,无匹配时返回 nil
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。
//
// 参数:
// - name: location 名称
//
// 返回值:
// - *NamedMatcher: 命名匹配器,不存在时返回 nil
func (e *LocationEngine) GetNamed(name string) *NamedMatcher {
return e.namedMatchers[name]
}
// MarkInitialized 标记初始化完成。
//
// 调用后所有 Add 方法均返回错误,确保运行时安全。
func (e *LocationEngine) MarkInitialized() {
e.initialized = true
e.prefixPriorityTree.MarkInitialized()
e.prefixTree.MarkInitialized()
}
// checkConflict 检查路径冲突。
//
// 参数:
// - path: 待注册的路径
// - locationType: location 类型
//
// 返回值:
// - error: 路径已存在时返回冲突错误
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 风格的正则模式。
//
// 支持以下前缀:
// - ~: 大小写不敏感正则
// - ~*: 大小写不敏感正则(同上)
// - ^~: 前缀优先(非正则,但在此解析)
//
// 参数:
// - pattern: 原始模式字符串
//
// 返回值:
// - cleanPattern: 去除前缀后的正则模式
// - caseInsensitive: 是否大小写不敏感
// - isRegex: 是否为正则模式
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 编译正则表达式,失败返回 nil。
//
// 参数:
// - pattern: 正则表达式模式
//
// 返回值:
// - *regexp.Regexp: 编译后的正则表达式,失败时返回 nil
func MustCompileRegex(pattern string) *regexp.Regexp {
re, err := regexp.Compile(pattern)
if err != nil {
return nil
}
return re
}