refactor(matcher): 提取 LocationType 常量并优化结构体字段布局
- 添加 LocationType 常量定义替代硬编码字符串 - 优化 MatchResult、ExactMatcher、NamedMatcher 结构体字段顺序 - RadixTree.Insert 添加 locationType 参数用于调试追踪 - 更新测试代码适配新接口 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
c418baeb90
commit
ca03c121d3
@ -19,7 +19,7 @@ func BenchmarkRadixTree_Insert(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, p := range paths {
|
||||
tree.Insert(p, handler, i)
|
||||
tree.Insert(p, handler, i, "prefix")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -30,7 +30,7 @@ func BenchmarkRadixTree_Find(b *testing.B) {
|
||||
|
||||
paths := []string{"/", "/api", "/api/v1", "/api/v2/users/123"}
|
||||
for i, p := range paths {
|
||||
tree.Insert(p, handler, i+1)
|
||||
tree.Insert(p, handler, i+1, "prefix")
|
||||
}
|
||||
tree.MarkInitialized()
|
||||
|
||||
|
||||
@ -6,8 +6,8 @@ import (
|
||||
|
||||
// ExactMatcher Hash Map 精确匹配
|
||||
type ExactMatcher struct {
|
||||
path string
|
||||
handler fasthttp.RequestHandler
|
||||
path string
|
||||
priority int
|
||||
}
|
||||
|
||||
@ -31,6 +31,6 @@ func (m *ExactMatcher) Result() *MatchResult {
|
||||
Handler: m.handler,
|
||||
Path: m.path,
|
||||
Priority: m.priority,
|
||||
LocationType: "exact",
|
||||
LocationType: LocationTypeExact,
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ func TestLocationEngine_NginxPriority(t *testing.T) {
|
||||
|
||||
// 测试 ^~ 阻止正则
|
||||
result = engine.Match("/api/test.php")
|
||||
if result.LocationType != "prefix" {
|
||||
if result.LocationType != "prefix_priority" {
|
||||
t.Errorf("^~ should block regex, got %s", result.LocationType)
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,24 +10,13 @@ import (
|
||||
|
||||
// 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
|
||||
exactMatchers map[string]*ExactMatcher
|
||||
namedMatchers map[string]*NamedMatcher
|
||||
registeredPaths map[string]string
|
||||
regexMatchers []*RegexMatcher
|
||||
initialized bool
|
||||
}
|
||||
|
||||
// NewLocationEngine 创建新引擎
|
||||
@ -67,7 +56,7 @@ func (e *LocationEngine) AddPrefixPriority(path string, handler fasthttp.Request
|
||||
return err
|
||||
}
|
||||
|
||||
return e.prefixPriorityTree.Insert(path, handler, 2)
|
||||
return e.prefixPriorityTree.Insert(path, handler, 2, "prefix_priority")
|
||||
}
|
||||
|
||||
// AddRegex 添加正则匹配 location
|
||||
@ -95,7 +84,7 @@ func (e *LocationEngine) AddPrefix(path string, handler fasthttp.RequestHandler)
|
||||
return err
|
||||
}
|
||||
|
||||
return e.prefixTree.Insert(path, handler, 4)
|
||||
return e.prefixTree.Insert(path, handler, 4, "prefix")
|
||||
}
|
||||
|
||||
// AddNamed 添加命名 location
|
||||
|
||||
@ -2,15 +2,23 @@ package matcher
|
||||
|
||||
import "github.com/valyala/fasthttp"
|
||||
|
||||
// LocationType 常量定义
|
||||
const (
|
||||
LocationTypeExact = "exact"
|
||||
LocationTypePrefix = "prefix"
|
||||
LocationTypePrefixPriority = "prefix_priority"
|
||||
LocationTypeRegex = "regex"
|
||||
LocationTypeRegexCaseless = "regex_caseless"
|
||||
LocationTypeNamed = "named"
|
||||
)
|
||||
|
||||
// MatchResult 匹配结果
|
||||
type MatchResult struct {
|
||||
Captures map[string]string // 正则捕获组
|
||||
Handler fasthttp.RequestHandler
|
||||
LocationType string
|
||||
Path string
|
||||
Priority int
|
||||
LocationType string
|
||||
|
||||
// 正则捕获组
|
||||
Captures map[string]string
|
||||
}
|
||||
|
||||
// Matcher 接口
|
||||
|
||||
@ -4,8 +4,8 @@ import "github.com/valyala/fasthttp"
|
||||
|
||||
// NamedMatcher @命名 location
|
||||
type NamedMatcher struct {
|
||||
name string
|
||||
handler fasthttp.RequestHandler
|
||||
name string
|
||||
}
|
||||
|
||||
// NewNamedMatcher 创建命名匹配器
|
||||
@ -17,7 +17,7 @@ func NewNamedMatcher(name string, handler fasthttp.RequestHandler) *NamedMatcher
|
||||
}
|
||||
|
||||
// Match 检查命名是否匹配(命名 location 不使用 path 匹配)
|
||||
func (m *NamedMatcher) Match(path string) bool {
|
||||
func (m *NamedMatcher) Match(_ string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ func (m *NamedMatcher) Result() *MatchResult {
|
||||
Handler: m.handler,
|
||||
Path: "@" + m.name,
|
||||
Priority: 0,
|
||||
LocationType: "named",
|
||||
LocationType: LocationTypeNamed,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ func NewPrefixMatcher() *PrefixMatcher {
|
||||
|
||||
// AddPath 添加路径
|
||||
func (pm *PrefixMatcher) AddPath(path string, handler fasthttp.RequestHandler) error {
|
||||
return pm.tree.Insert(path, handler, pm.priority)
|
||||
return pm.tree.Insert(path, handler, pm.priority, "prefix")
|
||||
}
|
||||
|
||||
// Match 前缀匹配,返回最长前缀匹配结果
|
||||
|
||||
@ -18,7 +18,7 @@ func NewPrefixPriorityMatcher() *PrefixPriorityMatcher {
|
||||
|
||||
// AddPath 添加路径
|
||||
func (ppm *PrefixPriorityMatcher) AddPath(path string, handler fasthttp.RequestHandler) error {
|
||||
return ppm.tree.Insert(path, handler, ppm.priority)
|
||||
return ppm.tree.Insert(path, handler, ppm.priority, "prefix_priority")
|
||||
}
|
||||
|
||||
// Match 前缀优先匹配,返回最长前缀匹配结果
|
||||
|
||||
@ -9,11 +9,12 @@ import (
|
||||
|
||||
// RadixNode Radix Tree 节点
|
||||
type RadixNode struct {
|
||||
prefix string
|
||||
handler fasthttp.RequestHandler
|
||||
children []*RadixNode
|
||||
isLeaf bool
|
||||
priority int
|
||||
children []*RadixNode
|
||||
handler fasthttp.RequestHandler
|
||||
priority int
|
||||
isLeaf bool
|
||||
prefix string
|
||||
locationType string // exact/prefix/prefix_priority
|
||||
}
|
||||
|
||||
// RadixTree 前缀匹配 Radix Tree
|
||||
@ -30,29 +31,31 @@ func NewRadixTree() *RadixTree {
|
||||
}
|
||||
|
||||
// Insert 插入路径到 Radix Tree(startup-only)
|
||||
func (t *RadixTree) Insert(path string, handler fasthttp.RequestHandler, priority int) error {
|
||||
func (t *RadixTree) Insert(path string, handler fasthttp.RequestHandler, priority int, locationType string) error {
|
||||
if t.initialized {
|
||||
return errors.New("RadixTree already initialized")
|
||||
}
|
||||
return t.insertNode(nil, t.root, path, handler, priority)
|
||||
return t.insertNode(nil, t.root, path, handler, priority, locationType)
|
||||
}
|
||||
|
||||
// insertNode 完整路径分割插入算法
|
||||
func (t *RadixTree) insertNode(parent *RadixNode, node *RadixNode, path string, handler fasthttp.RequestHandler, priority int) 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 {
|
||||
if path == "" {
|
||||
node.handler = handler
|
||||
node.priority = priority
|
||||
node.isLeaf = true
|
||||
node.locationType = locationType
|
||||
return nil
|
||||
}
|
||||
// 创建新子节点
|
||||
newNode := &RadixNode{
|
||||
prefix: path,
|
||||
handler: handler,
|
||||
isLeaf: true,
|
||||
priority: priority,
|
||||
prefix: path,
|
||||
handler: handler,
|
||||
isLeaf: true,
|
||||
priority: priority,
|
||||
locationType: locationType,
|
||||
}
|
||||
node.children = append(node.children, newNode)
|
||||
return nil
|
||||
@ -77,22 +80,24 @@ func (t *RadixTree) insertNode(parent *RadixNode, node *RadixNode, path string,
|
||||
node.handler = handler
|
||||
node.priority = priority
|
||||
node.isLeaf = true
|
||||
node.locationType = locationType
|
||||
return nil
|
||||
}
|
||||
|
||||
// 搜索匹配剩余路径的子节点
|
||||
for _, child := range node.children {
|
||||
if strings.HasPrefix(remaining, child.prefix) {
|
||||
return t.insertNode(node, child, remaining, handler, priority)
|
||||
return t.insertNode(node, child, remaining, handler, priority, locationType)
|
||||
}
|
||||
}
|
||||
|
||||
// 无匹配子节点,创建新子节点
|
||||
newNode := &RadixNode{
|
||||
prefix: remaining,
|
||||
handler: handler,
|
||||
isLeaf: true,
|
||||
priority: priority,
|
||||
prefix: remaining,
|
||||
handler: handler,
|
||||
isLeaf: true,
|
||||
priority: priority,
|
||||
locationType: locationType,
|
||||
}
|
||||
node.children = append(node.children, newNode)
|
||||
return nil
|
||||
@ -110,10 +115,11 @@ func (t *RadixTree) insertNode(parent *RadixNode, node *RadixNode, path string,
|
||||
|
||||
// 创建新节点保存剩余路径
|
||||
newNode := &RadixNode{
|
||||
prefix: path[commonLen:],
|
||||
handler: handler,
|
||||
isLeaf: true,
|
||||
priority: priority,
|
||||
prefix: path[commonLen:],
|
||||
handler: handler,
|
||||
isLeaf: true,
|
||||
priority: priority,
|
||||
locationType: locationType,
|
||||
}
|
||||
|
||||
// 将原节点和新节点作为 splitNode 的子节点
|
||||
@ -159,7 +165,7 @@ func (t *RadixTree) searchLongest(node *RadixNode, path string, bestMatch *Match
|
||||
Handler: node.handler,
|
||||
Path: node.prefix,
|
||||
Priority: node.priority,
|
||||
LocationType: "prefix",
|
||||
LocationType: node.locationType,
|
||||
}
|
||||
|
||||
// nil-safe 优先级比较 + 长度比较
|
||||
|
||||
@ -11,7 +11,7 @@ func TestRadixTree_Insert_EmptyNode(t *testing.T) {
|
||||
tree := NewRadixTree()
|
||||
handler := func(ctx *fasthttp.RequestCtx) {}
|
||||
|
||||
err := tree.Insert("/api", handler, 1)
|
||||
err := tree.Insert("/api", handler, 1, "prefix")
|
||||
if err != nil {
|
||||
t.Fatalf("insert failed: %v", err)
|
||||
}
|
||||
@ -31,8 +31,8 @@ func TestRadixTree_Insert_CommonPrefix(t *testing.T) {
|
||||
handler1 := func(ctx *fasthttp.RequestCtx) { ctx.SetBodyString("1") }
|
||||
handler2 := func(ctx *fasthttp.RequestCtx) { ctx.SetBodyString("2") }
|
||||
|
||||
tree.Insert("/api", handler1, 1)
|
||||
tree.Insert("/api/users", handler2, 2)
|
||||
tree.Insert("/api", handler1, 1, "prefix")
|
||||
tree.Insert("/api/users", handler2, 2, "prefix")
|
||||
|
||||
result := tree.FindLongestPrefix("/api/users")
|
||||
if result == nil {
|
||||
@ -53,8 +53,8 @@ func TestRadixTree_Insert_NodeSplit(t *testing.T) {
|
||||
handler1 := func(ctx *fasthttp.RequestCtx) {}
|
||||
handler2 := func(ctx *fasthttp.RequestCtx) {}
|
||||
|
||||
tree.Insert("/abc", handler1, 1)
|
||||
tree.Insert("/abx", handler2, 2)
|
||||
tree.Insert("/abc", handler1, 1, "prefix")
|
||||
tree.Insert("/abx", handler2, 2, "prefix")
|
||||
|
||||
// 应该正确分割 /ab 公共前缀
|
||||
result := tree.FindLongestPrefix("/abc")
|
||||
@ -67,9 +67,9 @@ func TestRadixTree_FindLongestPrefix(t *testing.T) {
|
||||
tree := NewRadixTree()
|
||||
handler := func(ctx *fasthttp.RequestCtx) {}
|
||||
|
||||
tree.Insert("/", handler, 1)
|
||||
tree.Insert("/api", handler, 2)
|
||||
tree.Insert("/api/v1", handler, 3)
|
||||
tree.Insert("/", handler, 1, "prefix")
|
||||
tree.Insert("/api", handler, 2, "prefix")
|
||||
tree.Insert("/api/v1", handler, 3, "prefix")
|
||||
|
||||
// "/" has priority 1 (wins), "/api" has 2, "/api/v1" has 3
|
||||
// Lower number = higher priority
|
||||
@ -86,10 +86,10 @@ func TestRadixTree_Insert_AfterInitialized(t *testing.T) {
|
||||
tree := NewRadixTree()
|
||||
handler := func(ctx *fasthttp.RequestCtx) {}
|
||||
|
||||
tree.Insert("/api", handler, 1)
|
||||
tree.Insert("/api", handler, 1, "prefix")
|
||||
tree.MarkInitialized()
|
||||
|
||||
err := tree.Insert("/api/v2", handler, 2)
|
||||
err := tree.Insert("/api/v2", handler, 2, "prefix")
|
||||
if err == nil {
|
||||
t.Error("should fail when inserting after initialized")
|
||||
}
|
||||
@ -99,8 +99,8 @@ func TestRadixTree_Insert_DuplicatePath(t *testing.T) {
|
||||
tree := NewRadixTree()
|
||||
handler := func(ctx *fasthttp.RequestCtx) {}
|
||||
|
||||
tree.Insert("/api", handler, 1)
|
||||
err := tree.Insert("/api", handler, 2)
|
||||
tree.Insert("/api", handler, 1, "prefix")
|
||||
err := tree.Insert("/api", handler, 2, "prefix")
|
||||
if err == nil {
|
||||
t.Error("should fail on duplicate path")
|
||||
}
|
||||
@ -110,7 +110,7 @@ func TestRadixTree_FindLongestPrefix_NoMatch(t *testing.T) {
|
||||
tree := NewRadixTree()
|
||||
handler := func(ctx *fasthttp.RequestCtx) {}
|
||||
|
||||
tree.Insert("/api", handler, 1)
|
||||
tree.Insert("/api", handler, 1, "prefix")
|
||||
|
||||
result := tree.FindLongestPrefix("/other")
|
||||
if result != nil {
|
||||
@ -123,8 +123,8 @@ func TestRadixTree_PriorityComparison(t *testing.T) {
|
||||
h1 := func(ctx *fasthttp.RequestCtx) {}
|
||||
h2 := func(ctx *fasthttp.RequestCtx) {}
|
||||
|
||||
tree.Insert("/api", h1, 5)
|
||||
tree.Insert("/api/users", h2, 2)
|
||||
tree.Insert("/api", h1, 5, "prefix")
|
||||
tree.Insert("/api/users", h2, 2, "prefix")
|
||||
|
||||
// Lower priority number wins
|
||||
result := tree.FindLongestPrefix("/api/users")
|
||||
@ -135,3 +135,24 @@ func TestRadixTree_PriorityComparison(t *testing.T) {
|
||||
t.Errorf("expected priority 2, got %d", result.Priority)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRadixTree_Insert_ExactMatch(t *testing.T) {
|
||||
// Case 3: path 完全匹配节点前缀,设置 handler
|
||||
tree := NewRadixTree()
|
||||
handler := func(ctx *fasthttp.RequestCtx) { ctx.SetBodyString("exact") }
|
||||
|
||||
// 先插入父路径
|
||||
tree.Insert("/api", handler, 1, "prefix")
|
||||
|
||||
// 再次插入相同路径(应该报错重复)
|
||||
err := tree.Insert("/api", handler, 2, "prefix")
|
||||
if err == nil {
|
||||
t.Error("should return error for duplicate path")
|
||||
}
|
||||
|
||||
// 验证原 handler 未被覆盖
|
||||
result := tree.FindLongestPrefix("/api")
|
||||
if result == nil || result.Priority != 1 {
|
||||
t.Errorf("original handler should not be overwritten, got priority %d", result.Priority)
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,9 +10,9 @@ import (
|
||||
type RegexMatcher struct {
|
||||
pattern *regexp.Regexp
|
||||
handler fasthttp.RequestHandler
|
||||
captures map[string]string
|
||||
priority int
|
||||
caseInsensitive bool
|
||||
captures map[string]string
|
||||
}
|
||||
|
||||
// NewRegexMatcher 创建正则匹配器
|
||||
@ -47,9 +47,9 @@ func (m *RegexMatcher) Match(path string) bool {
|
||||
|
||||
// Result 返回匹配结果
|
||||
func (m *RegexMatcher) Result() *MatchResult {
|
||||
locType := "regex"
|
||||
locType := LocationTypeRegex
|
||||
if m.caseInsensitive {
|
||||
locType = "regex_caseless"
|
||||
locType = LocationTypeRegexCaseless
|
||||
}
|
||||
return &MatchResult{
|
||||
Handler: m.handler,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user