// Package matcher 提供 nginx 风格的 location 匹配引擎实现。 // // 该文件实现 Radix Tree(基数树)数据结构,用于高效的前缀路径匹配。 // // Radix Tree 是一种压缩前缀树,将共享同一前缀的路径合并到同一节点, // 相比普通 Trie 树大幅减少内存占用。查找时使用最长前缀匹配策略。 // // 作者:xfy package matcher import ( "bytes" "errors" "strings" "sync" "github.com/valyala/fasthttp" ) var matchResultPool = sync.Pool{ New: func() any { return &MatchResult{} }, } // RadixNode Radix Tree 节点。 // // 每个节点存储一个路径前缀,子节点存储剩余前缀。 // 叶子节点(isLeaf=true)包含具体的请求处理器。 type RadixNode struct { // children 子节点列表 children []*RadixNode // handler 请求处理器(仅叶子节点有效) handler fasthttp.RequestHandler // priority 匹配优先级 priority int // internal 是否为 internal location internal bool // isLeaf 是否为叶子节点(有 handler) isLeaf bool // prefix 当前节点的路径前缀 prefix string // locationType 位置类型(exact/prefix/prefix_priority) locationType string } // RadixTree 前缀匹配 Radix Tree。 // // 使用路径分割插入算法,支持最长前缀匹配查找。 // 初始化完成后可标记为只读状态,防止运行时修改。 type RadixTree struct { // root 根节点 root *RadixNode // initialized 是否已完成初始化(标记后不可插入) initialized bool } // NewRadixTree 创建新的 Radix Tree。 // // 返回值: // - *RadixTree: 空树实例,根节点已初始化 func NewRadixTree() *RadixTree { return &RadixTree{ root: &RadixNode{prefix: ""}, } } // Insert 插入路径到 Radix Tree。 // // 该函数仅在启动阶段使用,初始化完成后禁止插入。 // // 参数: // - path: 要插入的路径 // - handler: 匹配成功后的请求处理器 // - priority: 匹配优先级 // - locationType: 位置类型标识 // - internal: 是否为 internal location // // 返回值: // - error: 树已初始化或路径已存在时返回错误 func (t *RadixTree) Insert(path string, handler fasthttp.RequestHandler, priority int, locationType string, internal bool) error { if t.initialized { return errors.New("RadixTree already initialized") } return t.insertNode(nil, t.root, path, handler, priority, locationType, internal) } // insertNode 完整路径分割插入算法。 // // 算法分为四种情况: // - Case 1: 空节点直接设置 // - Case 2: 计算公共前缀长度 // - Case 3: 路径完全匹配节点前缀,递归处理剩余部分 // - Case 4: 需要分割节点,创建中间节点 // // 参数: // - parent: 父节点(根节点时为 nil) // - node: 当前节点 // - path: 待插入路径 // - handler: 请求处理器 // - priority: 优先级 // - locationType: 位置类型 // - internal: 是否为 internal location // // 返回值: // - error: 路径已存在时返回错误 func (t *RadixTree) insertNode(parent *RadixNode, node *RadixNode, path string, handler fasthttp.RequestHandler, priority int, locationType string, internal bool) 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 node.internal = internal return nil } // 创建新子节点 newNode := &RadixNode{ prefix: path, handler: handler, isLeaf: true, priority: priority, locationType: locationType, internal: internal, } node.children = append(node.children, newNode) return nil } // Case 2: 计算公共前缀长度 commonLen := 0 maxLen := minInt(len(node.prefix), len(path)) for commonLen < maxLen && node.prefix[commonLen] == path[commonLen] { commonLen++ } // Case 3: path 完全匹配节点前缀 if commonLen == len(node.prefix) { remaining := path[commonLen:] if remaining == "" { // 路径完全匹配,设置 handler if node.handler != nil { return errors.New("path already exists") } node.handler = handler node.priority = priority node.isLeaf = true node.locationType = locationType node.internal = internal return nil } // 搜索匹配剩余路径的子节点 for _, child := range node.children { if strings.HasPrefix(remaining, child.prefix) { return t.insertNode(node, child, remaining, handler, priority, locationType, internal) } } // 无匹配子节点,创建新子节点 newNode := &RadixNode{ prefix: remaining, handler: handler, isLeaf: true, priority: priority, locationType: locationType, internal: internal, } node.children = append(node.children, newNode) return nil } // Case 4: 需要分割节点(公共前缀 < 节点前缀) // 创建中间节点保存公共前缀 splitNode := &RadixNode{ prefix: node.prefix[:commonLen], children: []*RadixNode{}, } // 修改原节点为公共前缀之后的部分 node.prefix = node.prefix[commonLen:] // 创建新节点保存剩余路径 newNode := &RadixNode{ prefix: path[commonLen:], handler: handler, isLeaf: true, priority: priority, locationType: locationType, internal: internal, } // 将原节点和新节点作为 splitNode 的子节点 splitNode.children = append(splitNode.children, node) splitNode.children = append(splitNode.children, newNode) // 替换父节点的子节点引用 if parent == nil { t.root = splitNode } else { for i, child := range parent.children { if child == node { parent.children[i] = splitNode break } } } return nil } // FindLongestPrefix 查找最长前缀匹配。 // // 参数: // - path: 待匹配的请求路径 // // 返回值: // - *MatchResult: 最长前缀匹配结果,无匹配时返回 nil func (t *RadixTree) FindLongestPrefix(path []byte) *MatchResult { bestNode := t.searchLongest(t.root, path, nil) if bestNode == nil { return nil } result, _ := matchResultPool.Get().(*MatchResult) result.Handler = bestNode.handler result.Path = bestNode.prefix result.Priority = bestNode.priority result.LocationType = bestNode.locationType result.Internal = bestNode.internal return result } func (t *RadixTree) searchLongest(node *RadixNode, path []byte, bestNode *RadixNode) *RadixNode { if node == nil || len(path) == 0 { return bestNode } if !bytes.HasPrefix(path, []byte(node.prefix)) { return bestNode } remaining := path[len(node.prefix):] if node.handler != nil { if bestNode == nil || node.priority < bestNode.priority { bestNode = node } else if node.priority == bestNode.priority && len(node.prefix) > len(bestNode.prefix) { bestNode = node } } for _, child := range node.children { result := t.searchLongest(child, remaining, bestNode) if result != nil && result != bestNode { if bestNode == nil || result.priority < bestNode.priority { bestNode = result } else if result.priority == bestNode.priority && len(result.prefix) > len(bestNode.prefix) { bestNode = result } } } return bestNode } // ReleaseMatchResult 将 MatchResult 归还到对象池。 func ReleaseMatchResult(r *MatchResult) { if r == nil { return } *r = MatchResult{} matchResultPool.Put(r) } // MarkInitialized 标记初始化完成。 // // 标记后 Insert 调用将返回错误,确保运行时不可变性。 func (t *RadixTree) MarkInitialized() { t.initialized = true } // minInt 返回两个整数中的较小值。 func minInt(a, b int) int { if a < b { return a } return b }