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>
This commit is contained in:
parent
ad177e9640
commit
6094327620
@ -1,20 +1,39 @@
|
||||
// Package matcher 提供 nginx 风格的 location 匹配引擎实现。
|
||||
//
|
||||
// 该文件实现路径冲突检测器,防止同一路径被重复注册为不同类型。
|
||||
//
|
||||
// 作者:xfy
|
||||
package matcher
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ConflictDetector 冲突检测
|
||||
// ConflictDetector 路径冲突检测器。
|
||||
//
|
||||
// 维护已注册路径的映射,在添加新 location 时检查是否存在冲突。
|
||||
// 同一路径不能同时注册为多种匹配类型(如同时作为 exact 和 prefix)。
|
||||
type ConflictDetector struct {
|
||||
registeredPaths map[string]string // path -> location type
|
||||
// registeredPaths 已注册路径映射,key 为路径,value 为 location 类型
|
||||
registeredPaths map[string]string
|
||||
}
|
||||
|
||||
// NewConflictDetector 创建冲突检测器
|
||||
// NewConflictDetector 创建冲突检测器。
|
||||
//
|
||||
// 返回值:
|
||||
// - *ConflictDetector: 冲突检测器实例
|
||||
func NewConflictDetector() *ConflictDetector {
|
||||
return &ConflictDetector{
|
||||
registeredPaths: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册路径,返回冲突错误
|
||||
// Register 注册路径,如果已存在则返回冲突错误。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 待注册的路径
|
||||
// - locationType: location 类型(exact/prefix/regex 等)
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 路径已注册时返回冲突错误
|
||||
func (cd *ConflictDetector) Register(path, locationType string) error {
|
||||
if existing, ok := cd.registeredPaths[path]; ok {
|
||||
return fmt.Errorf("path conflict: '%s' already registered as '%s', trying to register as '%s'",
|
||||
@ -24,13 +43,22 @@ func (cd *ConflictDetector) Register(path, locationType string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exists 检查路径是否已注册
|
||||
// Exists 检查路径是否已注册。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 待检查的路径
|
||||
//
|
||||
// 返回值:
|
||||
// - bool: 已注册返回 true
|
||||
func (cd *ConflictDetector) Exists(path string) bool {
|
||||
_, ok := cd.registeredPaths[path]
|
||||
return ok
|
||||
}
|
||||
|
||||
// GetRegisteredPaths 返回所有已注册路径
|
||||
// GetRegisteredPaths 返回所有已注册路径的副本。
|
||||
//
|
||||
// 返回值:
|
||||
// - map[string]string: 路径到 location 类型的映射
|
||||
func (cd *ConflictDetector) GetRegisteredPaths() map[string]string {
|
||||
result := make(map[string]string, len(cd.registeredPaths))
|
||||
for k, v := range cd.registeredPaths {
|
||||
@ -39,12 +67,15 @@ func (cd *ConflictDetector) GetRegisteredPaths() map[string]string {
|
||||
return result
|
||||
}
|
||||
|
||||
// Remove 移除已注册路径
|
||||
// Remove 移除已注册的路径。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 要移除的路径
|
||||
func (cd *ConflictDetector) Remove(path string) {
|
||||
delete(cd.registeredPaths, path)
|
||||
}
|
||||
|
||||
// Clear 清空所有注册路径
|
||||
// Clear 清空所有已注册路径。
|
||||
func (cd *ConflictDetector) Clear() {
|
||||
cd.registeredPaths = make(map[string]string)
|
||||
}
|
||||
|
||||
@ -1,17 +1,36 @@
|
||||
// Package matcher 提供 nginx 风格的 location 匹配引擎实现。
|
||||
//
|
||||
// 该文件实现精确路径匹配器,使用 hash map 实现 O(1) 查找。
|
||||
//
|
||||
// 作者:xfy
|
||||
package matcher
|
||||
|
||||
import (
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
import "github.com/valyala/fasthttp"
|
||||
|
||||
// ExactMatcher Hash Map 精确匹配
|
||||
// ExactMatcher Hash Map 精确匹配器。
|
||||
//
|
||||
// 通过字符串等值比较实现 O(1) 时间复杂度的路径匹配,
|
||||
// 对应 nginx 的 = 修饰符。
|
||||
type ExactMatcher struct {
|
||||
handler fasthttp.RequestHandler
|
||||
path string
|
||||
// handler 请求处理器
|
||||
handler fasthttp.RequestHandler
|
||||
|
||||
// path 精确匹配路径
|
||||
path string
|
||||
|
||||
// priority 匹配优先级,精确匹配为 1(最高)
|
||||
priority int
|
||||
}
|
||||
|
||||
// NewExactMatcher 创建精确匹配器
|
||||
// NewExactMatcher 创建精确匹配器。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 精确匹配的路径
|
||||
// - handler: 匹配成功后的请求处理器
|
||||
// - priority: 优先级(通常设为 1)
|
||||
//
|
||||
// 返回值:
|
||||
// - *ExactMatcher: 精确匹配器实例
|
||||
func NewExactMatcher(path string, handler fasthttp.RequestHandler, priority int) *ExactMatcher {
|
||||
return &ExactMatcher{
|
||||
path: path,
|
||||
@ -20,12 +39,21 @@ func NewExactMatcher(path string, handler fasthttp.RequestHandler, priority int)
|
||||
}
|
||||
}
|
||||
|
||||
// Match 检查路径是否精确匹配
|
||||
// Match 检查路径是否精确匹配。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 待检查的请求路径
|
||||
//
|
||||
// 返回值:
|
||||
// - bool: 路径完全相等时返回 true
|
||||
func (m *ExactMatcher) Match(path string) bool {
|
||||
return m.path == path
|
||||
}
|
||||
|
||||
// Result 返回匹配结果
|
||||
// Result 返回匹配结果。
|
||||
//
|
||||
// 返回值:
|
||||
// - *MatchResult: 包含处理器和元数据的匹配结果
|
||||
func (m *ExactMatcher) Result() *MatchResult {
|
||||
return &MatchResult{
|
||||
Handler: m.handler,
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
// Package matcher 提供 nginx 风格的 location 匹配引擎实现。
|
||||
//
|
||||
// 该文件实现统一的 LocationEngine,整合所有匹配策略,
|
||||
// 按照 nginx 优先级顺序执行匹配。
|
||||
//
|
||||
// 匹配优先级从高到低:
|
||||
// 1. 精确匹配(=)
|
||||
// 2. 前缀优先匹配(^~)
|
||||
// 3. 正则匹配(~, ~*)
|
||||
// 4. 普通前缀匹配
|
||||
//
|
||||
// 作者:xfy
|
||||
package matcher
|
||||
|
||||
import (
|
||||
@ -8,18 +20,44 @@ import (
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
// LocationEngine 统一匹配引擎
|
||||
// LocationEngine 统一匹配引擎。
|
||||
//
|
||||
// 整合所有 location 匹配策略,按照 nginx 优先级顺序执行:
|
||||
// - 精确匹配:O(1) hash map 查找
|
||||
// - 前缀优先:Radix Tree 最长前缀匹配
|
||||
// - 正则匹配:按注册顺序逐个尝试
|
||||
// - 普通前缀:Radix Tree 最长前缀匹配
|
||||
//
|
||||
// 注意事项:
|
||||
// - 调用 MarkInitialized 后不可再添加 location
|
||||
// - 正则匹配器按注册顺序执行,配置时应将高频模式放在前面
|
||||
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
|
||||
// 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 创建新引擎
|
||||
// NewLocationEngine 创建新的匹配引擎实例。
|
||||
//
|
||||
// 返回值:
|
||||
// - *LocationEngine: 初始化的引擎实例
|
||||
func NewLocationEngine() *LocationEngine {
|
||||
return &LocationEngine{
|
||||
exactMatchers: make(map[string]*ExactMatcher),
|
||||
@ -31,7 +69,14 @@ func NewLocationEngine() *LocationEngine {
|
||||
}
|
||||
}
|
||||
|
||||
// AddExact 添加精确匹配 location
|
||||
// AddExact 添加精确匹配 location。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 精确匹配路径
|
||||
// - handler: 请求处理器
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 引擎已初始化或路径冲突时返回错误
|
||||
func (e *LocationEngine) AddExact(path string, handler fasthttp.RequestHandler) error {
|
||||
if e.initialized {
|
||||
return errors.New("LocationEngine already initialized")
|
||||
@ -46,7 +91,14 @@ func (e *LocationEngine) AddExact(path string, handler fasthttp.RequestHandler)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPrefixPriority 添加 ^~ 前缀优先匹配 location
|
||||
// AddPrefixPriority 添加 ^~ 前缀优先匹配 location。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 前缀优先路径
|
||||
// - handler: 请求处理器
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 引擎已初始化或路径冲突时返回错误
|
||||
func (e *LocationEngine) AddPrefixPriority(path string, handler fasthttp.RequestHandler) error {
|
||||
if e.initialized {
|
||||
return errors.New("LocationEngine already initialized")
|
||||
@ -59,7 +111,15 @@ func (e *LocationEngine) AddPrefixPriority(path string, handler fasthttp.Request
|
||||
return e.prefixPriorityTree.Insert(path, handler, 2, "prefix_priority")
|
||||
}
|
||||
|
||||
// AddRegex 添加正则匹配 location
|
||||
// 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")
|
||||
@ -74,7 +134,14 @@ func (e *LocationEngine) AddRegex(pattern string, handler fasthttp.RequestHandle
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPrefix 添加普通前缀匹配 location
|
||||
// AddPrefix 添加普通前缀匹配 location。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 前缀路径
|
||||
// - handler: 请求处理器
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 引擎已初始化或路径冲突时返回错误
|
||||
func (e *LocationEngine) AddPrefix(path string, handler fasthttp.RequestHandler) error {
|
||||
if e.initialized {
|
||||
return errors.New("LocationEngine already initialized")
|
||||
@ -87,7 +154,16 @@ func (e *LocationEngine) AddPrefix(path string, handler fasthttp.RequestHandler)
|
||||
return e.prefixTree.Insert(path, handler, 4, "prefix")
|
||||
}
|
||||
|
||||
// AddNamed 添加命名 location
|
||||
// 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")
|
||||
@ -102,8 +178,19 @@ func (e *LocationEngine) AddNamed(name string, handler fasthttp.RequestHandler)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Match 统一匹配入口
|
||||
// nginx 优先级:精确匹配 → 前缀优先(^~) → 正则 → 普通前缀
|
||||
// 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 {
|
||||
@ -129,19 +216,34 @@ func (e *LocationEngine) Match(path string) *MatchResult {
|
||||
return e.prefixTree.FindLongestPrefix(path)
|
||||
}
|
||||
|
||||
// GetNamed 获取命名 location
|
||||
// GetNamed 获取命名 location。
|
||||
//
|
||||
// 参数:
|
||||
// - name: location 名称
|
||||
//
|
||||
// 返回值:
|
||||
// - *NamedMatcher: 命名匹配器,不存在时返回 nil
|
||||
func (e *LocationEngine) GetNamed(name string) *NamedMatcher {
|
||||
return e.namedMatchers[name]
|
||||
}
|
||||
|
||||
// MarkInitialized 标记初始化完成
|
||||
// MarkInitialized 标记初始化完成。
|
||||
//
|
||||
// 调用后所有 Add 方法均返回错误,确保运行时安全。
|
||||
func (e *LocationEngine) MarkInitialized() {
|
||||
e.initialized = true
|
||||
e.prefixPriorityTree.MarkInitialized()
|
||||
e.prefixTree.MarkInitialized()
|
||||
}
|
||||
|
||||
// checkConflict 检查路径冲突
|
||||
// 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'",
|
||||
@ -151,7 +253,20 @@ func (e *LocationEngine) checkConflict(path, locationType string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseRegexPattern 解析 nginx 风格的正则模式(支持 ^~ ~ ~* 前缀)
|
||||
// ParseRegexPattern 解析 nginx 风格的正则模式。
|
||||
//
|
||||
// 支持以下前缀:
|
||||
// - ~: 大小写不敏感正则
|
||||
// - ~*: 大小写不敏感正则(同上)
|
||||
// - ^~: 前缀优先(非正则,但在此解析)
|
||||
//
|
||||
// 参数:
|
||||
// - pattern: 原始模式字符串
|
||||
//
|
||||
// 返回值:
|
||||
// - cleanPattern: 去除前缀后的正则模式
|
||||
// - caseInsensitive: 是否大小写不敏感
|
||||
// - isRegex: 是否为正则模式
|
||||
func ParseRegexPattern(pattern string) (cleanPattern string, caseInsensitive bool, isRegex bool) {
|
||||
if len(pattern) == 0 {
|
||||
return pattern, false, false
|
||||
@ -173,7 +288,13 @@ func ParseRegexPattern(pattern string) (cleanPattern string, caseInsensitive boo
|
||||
return pattern, false, false
|
||||
}
|
||||
|
||||
// MustCompileRegex 编译正则表达式,失败返回原始字符串
|
||||
// MustCompileRegex 编译正则表达式,失败返回 nil。
|
||||
//
|
||||
// 参数:
|
||||
// - pattern: 正则表达式模式
|
||||
//
|
||||
// 返回值:
|
||||
// - *regexp.Regexp: 编译后的正则表达式,失败时返回 nil
|
||||
func MustCompileRegex(pattern string) *regexp.Regexp {
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
|
||||
@ -1,28 +1,63 @@
|
||||
// Package matcher 提供 nginx 风格的 location 匹配引擎实现。
|
||||
//
|
||||
// 该包实现了多种 location 匹配策略,包括:
|
||||
// - 精确匹配(exact):完全匹配请求路径
|
||||
// - 前缀匹配(prefix):基于 Radix Tree 的最长前缀匹配
|
||||
// - 前缀优先匹配(prefix_priority):^~ 类型,跳过正则匹配
|
||||
// - 正则匹配(regex/regex_caseless):支持命名捕获组
|
||||
// - 命名匹配(named):@name 形式的内部跳转目标
|
||||
//
|
||||
// 主要用途:
|
||||
// 用于反向代理模块根据请求路径选择对应的后端处理器,
|
||||
// 优先级顺序与 nginx 一致:精确 > 前缀优先(^~) > 正则(~,~*) > 普通前缀
|
||||
//
|
||||
// 注意事项:
|
||||
// - Radix Tree 在初始化完成后不可修改(MarkInitialized 后 Insert 返回错误)
|
||||
// - 正则匹配器按注册顺序执行,先匹配到的优先
|
||||
// - 所有匹配器均为非并发安全,应在启动阶段完成配置
|
||||
//
|
||||
// 作者:xfy
|
||||
package matcher
|
||||
|
||||
import "github.com/valyala/fasthttp"
|
||||
|
||||
// LocationType 常量定义
|
||||
// LocationType 常量定义,表示不同 location 匹配类型。
|
||||
const (
|
||||
LocationTypeExact = "exact"
|
||||
LocationTypePrefix = "prefix"
|
||||
LocationTypePrefixPriority = "prefix_priority"
|
||||
LocationTypeRegex = "regex"
|
||||
LocationTypeRegexCaseless = "regex_caseless"
|
||||
LocationTypeNamed = "named"
|
||||
LocationTypeExact = "exact" // 精确匹配 =
|
||||
LocationTypePrefix = "prefix" // 普通前缀匹配
|
||||
LocationTypePrefixPriority = "prefix_priority" // 前缀优先匹配 ^~
|
||||
LocationTypeRegex = "regex" // 正则匹配 ~
|
||||
LocationTypeRegexCaseless = "regex_caseless" // 大小写不敏感正则匹配 ~*
|
||||
LocationTypeNamed = "named" // 命名匹配 @name
|
||||
)
|
||||
|
||||
// MatchResult 匹配结果
|
||||
// MatchResult 匹配结果。
|
||||
//
|
||||
// 包含匹配成功后的处理器、捕获组和位置类型信息。
|
||||
type MatchResult struct {
|
||||
Captures map[string]string // 正则捕获组
|
||||
Handler fasthttp.RequestHandler
|
||||
// Captures 正则表达式的命名捕获组
|
||||
Captures map[string]string
|
||||
|
||||
// Handler 匹配到的请求处理器
|
||||
Handler fasthttp.RequestHandler
|
||||
|
||||
// LocationType 匹配类型(exact/prefix/regex 等)
|
||||
LocationType string
|
||||
Path string
|
||||
Priority int
|
||||
|
||||
// Path 匹配的路径模式
|
||||
Path string
|
||||
|
||||
// Priority 匹配优先级(数值越小优先级越高)
|
||||
Priority int
|
||||
}
|
||||
|
||||
// Matcher 接口
|
||||
// Matcher 匹配器接口。
|
||||
//
|
||||
// 所有具体匹配器(ExactMatcher、RegexMatcher 等)均需实现此接口。
|
||||
type Matcher interface {
|
||||
// Match 检查给定路径是否匹配
|
||||
Match(path string) bool
|
||||
|
||||
// Result 返回匹配结果(包含 Handler 和元数据)
|
||||
Result() *MatchResult
|
||||
}
|
||||
|
||||
@ -1,14 +1,32 @@
|
||||
// Package matcher 提供 nginx 风格的 location 匹配引擎实现。
|
||||
//
|
||||
// 该文件实现命名 location 匹配器,用于内部跳转。
|
||||
//
|
||||
// 作者:xfy
|
||||
package matcher
|
||||
|
||||
import "github.com/valyala/fasthttp"
|
||||
|
||||
// NamedMatcher @命名 location
|
||||
// NamedMatcher 命名 location 匹配器。
|
||||
//
|
||||
// 对应 nginx 的 @name 语法,用于内部跳转(如 error_page、try_files)。
|
||||
// 命名 location 不直接匹配请求路径,而是通过名称引用。
|
||||
type NamedMatcher struct {
|
||||
// handler 请求处理器
|
||||
handler fasthttp.RequestHandler
|
||||
name string
|
||||
|
||||
// name location 名称(不含 @ 前缀)
|
||||
name string
|
||||
}
|
||||
|
||||
// NewNamedMatcher 创建命名匹配器
|
||||
// NewNamedMatcher 创建命名匹配器。
|
||||
//
|
||||
// 参数:
|
||||
// - name: location 名称
|
||||
// - handler: 关联的请求处理器
|
||||
//
|
||||
// 返回值:
|
||||
// - *NamedMatcher: 命名匹配器实例
|
||||
func NewNamedMatcher(name string, handler fasthttp.RequestHandler) *NamedMatcher {
|
||||
return &NamedMatcher{
|
||||
name: name,
|
||||
@ -16,12 +34,18 @@ func NewNamedMatcher(name string, handler fasthttp.RequestHandler) *NamedMatcher
|
||||
}
|
||||
}
|
||||
|
||||
// Match 检查命名是否匹配(命名 location 不使用 path 匹配)
|
||||
// Match 检查路径是否匹配。
|
||||
//
|
||||
// 命名 location 不使用路径匹配,始终返回 false。
|
||||
// 获取命名 location 应使用 LocationEngine.GetNamed()。
|
||||
func (m *NamedMatcher) Match(_ string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Result 返回匹配结果
|
||||
// Result 返回匹配结果。
|
||||
//
|
||||
// 返回值:
|
||||
// - *MatchResult: 包含处理器和命名 location 元数据的结果
|
||||
func (m *NamedMatcher) Result() *MatchResult {
|
||||
return &MatchResult{
|
||||
Handler: m.handler,
|
||||
@ -31,7 +55,10 @@ func (m *NamedMatcher) Result() *MatchResult {
|
||||
}
|
||||
}
|
||||
|
||||
// Name 返回命名 location 的名称
|
||||
// Name 返回命名 location 的名称。
|
||||
//
|
||||
// 返回值:
|
||||
// - string: location 名称
|
||||
func (m *NamedMatcher) Name() string {
|
||||
return m.name
|
||||
}
|
||||
|
||||
@ -1,14 +1,28 @@
|
||||
// Package matcher 提供 nginx 风格的 location 匹配引擎实现。
|
||||
//
|
||||
// 该文件实现前缀匹配器,基于 Radix Tree 实现最长前缀匹配。
|
||||
//
|
||||
// 作者:xfy
|
||||
package matcher
|
||||
|
||||
import "github.com/valyala/fasthttp"
|
||||
|
||||
// PrefixMatcher 普通前缀匹配器(封装 RadixTree)
|
||||
// PrefixMatcher 普通前缀匹配器(封装 RadixTree)。
|
||||
//
|
||||
// 使用 Radix Tree 数据结构存储前缀路径,
|
||||
// 查找时返回最长匹配前缀,对应 nginx 无修饰符前缀匹配。
|
||||
type PrefixMatcher struct {
|
||||
tree *RadixTree
|
||||
// tree 基数树,存储前缀路径
|
||||
tree *RadixTree
|
||||
|
||||
// priority 匹配优先级,普通前缀为 4(最低)
|
||||
priority int
|
||||
}
|
||||
|
||||
// NewPrefixMatcher 创建前缀匹配器
|
||||
// NewPrefixMatcher 创建前缀匹配器。
|
||||
//
|
||||
// 返回值:
|
||||
// - *PrefixMatcher: 前缀匹配器实例,内部已初始化 Radix Tree
|
||||
func NewPrefixMatcher() *PrefixMatcher {
|
||||
return &PrefixMatcher{
|
||||
tree: NewRadixTree(),
|
||||
@ -16,17 +30,32 @@ func NewPrefixMatcher() *PrefixMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
// AddPath 添加路径
|
||||
// AddPath 添加路径到前缀匹配器。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 前缀路径
|
||||
// - handler: 匹配成功后的请求处理器
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 路径重复或树已初始化时返回错误
|
||||
func (pm *PrefixMatcher) AddPath(path string, handler fasthttp.RequestHandler) error {
|
||||
return pm.tree.Insert(path, handler, pm.priority, "prefix")
|
||||
}
|
||||
|
||||
// Match 前缀匹配,返回最长前缀匹配结果
|
||||
// Match 前缀匹配,返回最长前缀匹配结果。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 待匹配的请求路径
|
||||
//
|
||||
// 返回值:
|
||||
// - *MatchResult: 最长前缀匹配结果,无匹配时返回 nil
|
||||
func (pm *PrefixMatcher) Match(path string) *MatchResult {
|
||||
return pm.tree.FindLongestPrefix(path)
|
||||
}
|
||||
|
||||
// MarkInitialized 标记初始化完成
|
||||
// MarkInitialized 标记初始化完成。
|
||||
//
|
||||
// 调用后不能再添加新路径,确保运行时线程安全。
|
||||
func (pm *PrefixMatcher) MarkInitialized() {
|
||||
pm.tree.MarkInitialized()
|
||||
}
|
||||
|
||||
@ -1,14 +1,28 @@
|
||||
// Package matcher 提供 nginx 风格的 location 匹配引擎实现。
|
||||
//
|
||||
// 该文件实现前缀优先匹配器(^~ 类型),比普通前缀匹配优先级更高。
|
||||
//
|
||||
// 作者:xfy
|
||||
package matcher
|
||||
|
||||
import "github.com/valyala/fasthttp"
|
||||
|
||||
// PrefixPriorityMatcher ^~ 类型前缀优先匹配器(封装 RadixTree)
|
||||
// PrefixPriorityMatcher 前缀优先匹配器(^~ 类型)。
|
||||
//
|
||||
// 基于 Radix Tree 实现,优先级为 2(仅次于精确匹配)。
|
||||
// 对应 nginx 的 ^~ 修饰符:匹配成功后跳过正则匹配阶段。
|
||||
type PrefixPriorityMatcher struct {
|
||||
tree *RadixTree
|
||||
// tree 基数树,存储前缀优先路径
|
||||
tree *RadixTree
|
||||
|
||||
// priority 匹配优先级,^~ 类型为 2
|
||||
priority int
|
||||
}
|
||||
|
||||
// NewPrefixPriorityMatcher 创建前缀优先匹配器
|
||||
// NewPrefixPriorityMatcher 创建前缀优先匹配器。
|
||||
//
|
||||
// 返回值:
|
||||
// - *PrefixPriorityMatcher: 前缀优先匹配器实例
|
||||
func NewPrefixPriorityMatcher() *PrefixPriorityMatcher {
|
||||
return &PrefixPriorityMatcher{
|
||||
tree: NewRadixTree(),
|
||||
@ -16,17 +30,32 @@ func NewPrefixPriorityMatcher() *PrefixPriorityMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
// AddPath 添加路径
|
||||
// AddPath 添加路径到前缀优先匹配器。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 前缀优先路径
|
||||
// - handler: 匹配成功后的请求处理器
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 路径重复或树已初始化时返回错误
|
||||
func (ppm *PrefixPriorityMatcher) AddPath(path string, handler fasthttp.RequestHandler) error {
|
||||
return ppm.tree.Insert(path, handler, ppm.priority, "prefix_priority")
|
||||
}
|
||||
|
||||
// Match 前缀优先匹配,返回最长前缀匹配结果
|
||||
// Match 前缀优先匹配,返回最长前缀匹配结果。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 待匹配的请求路径
|
||||
//
|
||||
// 返回值:
|
||||
// - *MatchResult: 最长前缀匹配结果,无匹配时返回 nil
|
||||
func (ppm *PrefixPriorityMatcher) Match(path string) *MatchResult {
|
||||
return ppm.tree.FindLongestPrefix(path)
|
||||
}
|
||||
|
||||
// MarkInitialized 标记初始化完成
|
||||
// MarkInitialized 标记初始化完成。
|
||||
//
|
||||
// 调用后不能再添加新路径。
|
||||
func (ppm *PrefixPriorityMatcher) MarkInitialized() {
|
||||
ppm.tree.MarkInitialized()
|
||||
}
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
// Package matcher 提供 nginx 风格的 location 匹配引擎实现。
|
||||
//
|
||||
// 该文件实现 Radix Tree(基数树)数据结构,用于高效的前缀路径匹配。
|
||||
//
|
||||
// Radix Tree 是一种压缩前缀树,将共享同一前缀的路径合并到同一节点,
|
||||
// 相比普通 Trie 树大幅减少内存占用。查找时使用最长前缀匹配策略。
|
||||
//
|
||||
// 作者:xfy
|
||||
package matcher
|
||||
|
||||
import (
|
||||
@ -7,30 +15,64 @@ import (
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
// RadixNode Radix Tree 节点
|
||||
// RadixNode Radix Tree 节点。
|
||||
//
|
||||
// 每个节点存储一个路径前缀,子节点存储剩余前缀。
|
||||
// 叶子节点(isLeaf=true)包含具体的请求处理器。
|
||||
type RadixNode struct {
|
||||
children []*RadixNode
|
||||
handler fasthttp.RequestHandler
|
||||
priority int
|
||||
isLeaf bool
|
||||
prefix string
|
||||
locationType string // exact/prefix/prefix_priority
|
||||
// children 子节点列表
|
||||
children []*RadixNode
|
||||
|
||||
// handler 请求处理器(仅叶子节点有效)
|
||||
handler fasthttp.RequestHandler
|
||||
|
||||
// priority 匹配优先级
|
||||
priority int
|
||||
|
||||
// isLeaf 是否为叶子节点(有 handler)
|
||||
isLeaf bool
|
||||
|
||||
// prefix 当前节点的路径前缀
|
||||
prefix string
|
||||
|
||||
// locationType 位置类型(exact/prefix/prefix_priority)
|
||||
locationType string
|
||||
}
|
||||
|
||||
// RadixTree 前缀匹配 Radix Tree
|
||||
// RadixTree 前缀匹配 Radix Tree。
|
||||
//
|
||||
// 使用路径分割插入算法,支持最长前缀匹配查找。
|
||||
// 初始化完成后可标记为只读状态,防止运行时修改。
|
||||
type RadixTree struct {
|
||||
root *RadixNode
|
||||
// root 根节点
|
||||
root *RadixNode
|
||||
|
||||
// initialized 是否已完成初始化(标记后不可插入)
|
||||
initialized bool
|
||||
}
|
||||
|
||||
// NewRadixTree 创建新 Radix Tree
|
||||
// NewRadixTree 创建新的 Radix Tree。
|
||||
//
|
||||
// 返回值:
|
||||
// - *RadixTree: 空树实例,根节点已初始化
|
||||
func NewRadixTree() *RadixTree {
|
||||
return &RadixTree{
|
||||
root: &RadixNode{prefix: ""},
|
||||
}
|
||||
}
|
||||
|
||||
// Insert 插入路径到 Radix Tree(startup-only)
|
||||
// Insert 插入路径到 Radix Tree。
|
||||
//
|
||||
// 该函数仅在启动阶段使用,初始化完成后禁止插入。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 要插入的路径
|
||||
// - handler: 匹配成功后的请求处理器
|
||||
// - priority: 匹配优先级
|
||||
// - locationType: 位置类型标识
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 树已初始化或路径已存在时返回错误
|
||||
func (t *RadixTree) Insert(path string, handler fasthttp.RequestHandler, priority int, locationType string) error {
|
||||
if t.initialized {
|
||||
return errors.New("RadixTree already initialized")
|
||||
@ -38,7 +80,24 @@ func (t *RadixTree) Insert(path string, handler fasthttp.RequestHandler, priorit
|
||||
return t.insertNode(nil, t.root, path, handler, priority, locationType)
|
||||
}
|
||||
|
||||
// insertNode 完整路径分割插入算法
|
||||
// insertNode 完整路径分割插入算法。
|
||||
//
|
||||
// 算法分为四种情况:
|
||||
// - Case 1: 空节点直接设置
|
||||
// - Case 2: 计算公共前缀长度
|
||||
// - Case 3: 路径完全匹配节点前缀,递归处理剩余部分
|
||||
// - Case 4: 需要分割节点,创建中间节点
|
||||
//
|
||||
// 参数:
|
||||
// - parent: 父节点(根节点时为 nil)
|
||||
// - node: 当前节点
|
||||
// - path: 待插入路径
|
||||
// - handler: 请求处理器
|
||||
// - priority: 优先级
|
||||
// - locationType: 位置类型
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 路径已存在时返回错误
|
||||
func (t *RadixTree) insertNode(parent *RadixNode, node *RadixNode, path string, handler fasthttp.RequestHandler, priority int, locationType string) error {
|
||||
// Case 1: 空节点(根节点),直接设置
|
||||
if node.prefix == "" && len(node.children) == 0 && node.handler == nil {
|
||||
@ -141,12 +200,30 @@ func (t *RadixTree) insertNode(parent *RadixNode, node *RadixNode, path string,
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindLongestPrefix 查找最长前缀匹配
|
||||
// FindLongestPrefix 查找最长前缀匹配。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 待匹配的请求路径
|
||||
//
|
||||
// 返回值:
|
||||
// - *MatchResult: 最长前缀匹配结果,无匹配时返回 nil
|
||||
func (t *RadixTree) FindLongestPrefix(path string) *MatchResult {
|
||||
return t.searchLongest(t.root, path, nil)
|
||||
}
|
||||
|
||||
// searchLongest 递归搜索最长前缀匹配
|
||||
// searchLongest 递归搜索最长前缀匹配。
|
||||
//
|
||||
// 匹配规则:
|
||||
// 1. 优先级数值越小越优先
|
||||
// 2. 相同优先级时,前缀越长越优先
|
||||
//
|
||||
// 参数:
|
||||
// - node: 当前搜索节点
|
||||
// - path: 剩余待匹配路径
|
||||
// - bestMatch: 当前最佳匹配
|
||||
//
|
||||
// 返回值:
|
||||
// - *MatchResult: 最佳匹配结果
|
||||
func (t *RadixTree) searchLongest(node *RadixNode, path string, bestMatch *MatchResult) *MatchResult {
|
||||
if node == nil || path == "" {
|
||||
return bestMatch
|
||||
@ -196,11 +273,14 @@ func (t *RadixTree) searchLongest(node *RadixNode, path string, bestMatch *Match
|
||||
return bestMatch
|
||||
}
|
||||
|
||||
// MarkInitialized 标记初始化完成
|
||||
// MarkInitialized 标记初始化完成。
|
||||
//
|
||||
// 标记后 Insert 调用将返回错误,确保运行时不可变性。
|
||||
func (t *RadixTree) MarkInitialized() {
|
||||
t.initialized = true
|
||||
}
|
||||
|
||||
// minInt 返回两个整数中的较小值。
|
||||
func minInt(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
// Package matcher 提供 nginx 风格的 location 匹配引擎实现。
|
||||
//
|
||||
// 该文件实现正则表达式匹配器,支持命名捕获组提取。
|
||||
//
|
||||
// 作者:xfy
|
||||
package matcher
|
||||
|
||||
import (
|
||||
@ -6,16 +11,38 @@ import (
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
// RegexMatcher 正则匹配 + 捕获组
|
||||
// RegexMatcher 正则表达式匹配器。
|
||||
//
|
||||
// 使用 Go 标准库 regexp 编译正则模式,
|
||||
// 支持命名捕获组提取,对应 nginx 的 ~ 和 ~* 修饰符。
|
||||
type RegexMatcher struct {
|
||||
pattern *regexp.Regexp
|
||||
handler fasthttp.RequestHandler
|
||||
captures map[string]string
|
||||
priority int
|
||||
// pattern 编译后的正则表达式
|
||||
pattern *regexp.Regexp
|
||||
|
||||
// handler 匹配成功后的请求处理器
|
||||
handler fasthttp.RequestHandler
|
||||
|
||||
// captures 最后一次匹配提取的命名捕获组
|
||||
captures map[string]string
|
||||
|
||||
// priority 匹配优先级,正则匹配为 3
|
||||
priority int
|
||||
|
||||
// caseInsensitive 是否大小写不敏感(~* 模式)
|
||||
caseInsensitive bool
|
||||
}
|
||||
|
||||
// NewRegexMatcher 创建正则匹配器
|
||||
// NewRegexMatcher 创建正则匹配器。
|
||||
//
|
||||
// 参数:
|
||||
// - pattern: 正则表达式模式字符串
|
||||
// - handler: 匹配成功后的请求处理器
|
||||
// - priority: 优先级(通常设为 3)
|
||||
// - caseInsensitive: 是否大小写不敏感
|
||||
//
|
||||
// 返回值:
|
||||
// - *RegexMatcher: 正则匹配器实例
|
||||
// - error: 正则表达式编译失败时返回错误
|
||||
func NewRegexMatcher(pattern string, handler fasthttp.RequestHandler, priority int, caseInsensitive bool) (*RegexMatcher, error) {
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
@ -31,7 +58,18 @@ func NewRegexMatcher(pattern string, handler fasthttp.RequestHandler, priority i
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MustRegexMatcher 创建正则匹配器,失败时 panic
|
||||
// MustRegexMatcher 创建正则匹配器,编译失败时 panic。
|
||||
//
|
||||
// 适用于启动时配置加载场景,配置错误直接终止程序。
|
||||
//
|
||||
// 参数:
|
||||
// - pattern: 正则表达式模式字符串
|
||||
// - handler: 匹配成功后的请求处理器
|
||||
// - priority: 优先级
|
||||
// - caseInsensitive: 是否大小写不敏感
|
||||
//
|
||||
// 返回值:
|
||||
// - *RegexMatcher: 正则匹配器实例
|
||||
func MustRegexMatcher(pattern string, handler fasthttp.RequestHandler, priority int, caseInsensitive bool) *RegexMatcher {
|
||||
m, err := NewRegexMatcher(pattern, handler, priority, caseInsensitive)
|
||||
if err != nil {
|
||||
@ -40,12 +78,21 @@ func MustRegexMatcher(pattern string, handler fasthttp.RequestHandler, priority
|
||||
return m
|
||||
}
|
||||
|
||||
// Match 检查路径是否正则匹配
|
||||
// Match 检查路径是否匹配正则表达式。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 待匹配的请求路径
|
||||
//
|
||||
// 返回值:
|
||||
// - bool: 正则匹配成功时返回 true
|
||||
func (m *RegexMatcher) Match(path string) bool {
|
||||
return m.pattern.MatchString(path)
|
||||
}
|
||||
|
||||
// Result 返回匹配结果
|
||||
// Result 返回匹配结果。
|
||||
//
|
||||
// 返回值:
|
||||
// - *MatchResult: 包含处理器和匹配元数据的结果
|
||||
func (m *RegexMatcher) Result() *MatchResult {
|
||||
locType := LocationTypeRegex
|
||||
if m.caseInsensitive {
|
||||
@ -60,7 +107,15 @@ func (m *RegexMatcher) Result() *MatchResult {
|
||||
}
|
||||
}
|
||||
|
||||
// GetCaptures 获取正则捕获组
|
||||
// GetCaptures 获取正则表达式在当前路径上的命名捕获组。
|
||||
//
|
||||
// 该方法在每次匹配后调用,提取捕获组数据供后续使用。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 当前请求路径
|
||||
//
|
||||
// 返回值:
|
||||
// - map[string]string: 命名捕获组映射,无捕获时返回 nil
|
||||
func (m *RegexMatcher) GetCaptures(path string) map[string]string {
|
||||
matches := m.pattern.FindStringSubmatch(path)
|
||||
if matches == nil {
|
||||
@ -71,7 +126,7 @@ func (m *RegexMatcher) GetCaptures(path string) map[string]string {
|
||||
names := m.pattern.SubexpNames()
|
||||
for i, name := range names {
|
||||
if i == 0 {
|
||||
continue
|
||||
continue // 跳过全匹配(索引 0)
|
||||
}
|
||||
if name != "" && i < len(matches) {
|
||||
result[name] = matches[i]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user