From 609432762075e81db9c464443f22ffbd10089edb Mon Sep 17 00:00:00 2001 From: xfy Date: Mon, 20 Apr 2026 10:59:27 +0800 Subject: [PATCH] =?UTF-8?q?docs(matcher):=20=E4=B8=BA=20location=20?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E5=BC=95=E6=93=8E=E6=B7=BB=E5=8A=A0=E6=A0=87?= =?UTF-8?q?=E5=87=86=E5=8C=96=20godoc=20=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为 matcher 包所有文件添加完整文档注释: - conflict: 路径冲突检测器 - exact: 精确路径匹配器(O(1) hash map) - location: 统一匹配引擎(整合所有策略) - matcher: 匹配结果和接口定义 - named: 命名捕获组匹配器 - prefix: 普通前缀匹配器 - prefix_priority: 前缀优先匹配器(^~) - radix: Radix Tree 最长前缀匹配 - regex: 正则表达式匹配器 注释说明匹配优先级顺序(精确 > 前缀优先 > 正则 > 普通前缀), 以及各匹配器的使用方法和性能特点。 Co-Authored-By: Claude Opus 4.7 --- internal/matcher/conflict.go | 47 ++++++-- internal/matcher/exact.go | 46 ++++++-- internal/matcher/location.go | 163 ++++++++++++++++++++++++---- internal/matcher/matcher.go | 61 ++++++++--- internal/matcher/named.go | 39 ++++++- internal/matcher/prefix.go | 41 ++++++- internal/matcher/prefix_priority.go | 41 ++++++- internal/matcher/radix.go | 110 ++++++++++++++++--- internal/matcher/regex.go | 77 +++++++++++-- 9 files changed, 530 insertions(+), 95 deletions(-) diff --git a/internal/matcher/conflict.go b/internal/matcher/conflict.go index 7b641a0..fe28b06 100644 --- a/internal/matcher/conflict.go +++ b/internal/matcher/conflict.go @@ -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) } diff --git a/internal/matcher/exact.go b/internal/matcher/exact.go index a34f391..40915f4 100644 --- a/internal/matcher/exact.go +++ b/internal/matcher/exact.go @@ -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, diff --git a/internal/matcher/location.go b/internal/matcher/location.go index 22e3b2d..5ef547c 100644 --- a/internal/matcher/location.go +++ b/internal/matcher/location.go @@ -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 { diff --git a/internal/matcher/matcher.go b/internal/matcher/matcher.go index 1bac513..755d547 100644 --- a/internal/matcher/matcher.go +++ b/internal/matcher/matcher.go @@ -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 } diff --git a/internal/matcher/named.go b/internal/matcher/named.go index 3386e95..077d49b 100644 --- a/internal/matcher/named.go +++ b/internal/matcher/named.go @@ -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 } diff --git a/internal/matcher/prefix.go b/internal/matcher/prefix.go index f4f6223..02264bf 100644 --- a/internal/matcher/prefix.go +++ b/internal/matcher/prefix.go @@ -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() } diff --git a/internal/matcher/prefix_priority.go b/internal/matcher/prefix_priority.go index 67ce63b..a5ff73a 100644 --- a/internal/matcher/prefix_priority.go +++ b/internal/matcher/prefix_priority.go @@ -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() } diff --git a/internal/matcher/radix.go b/internal/matcher/radix.go index 38de4c9..5eeb456 100644 --- a/internal/matcher/radix.go +++ b/internal/matcher/radix.go @@ -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 diff --git a/internal/matcher/regex.go b/internal/matcher/regex.go index 1ceb702..d6b58f9 100644 --- a/internal/matcher/regex.go +++ b/internal/matcher/regex.go @@ -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]