diff --git a/net/ghttp/ghttp.go b/net/ghttp/ghttp.go index 3a58745db8f..53139195b1a 100644 --- a/net/ghttp/ghttp.go +++ b/net/ghttp/ghttp.go @@ -15,6 +15,7 @@ import ( "github.com/gorilla/websocket" + "github.com/gogf/gf/v2/container/glist" "github.com/gogf/gf/v2/container/gmap" "github.com/gogf/gf/v2/container/gtype" "github.com/gogf/gf/v2/errors/gcode" @@ -38,7 +39,7 @@ type ( servers []*graceful.Server // Underlying http.Server array. serverCount *gtype.Int // Underlying http.Server number for internal usage. closeChan chan struct{} // Used for underlying server closing event notification. - serveTree map[string]any // The route maps tree. + serveTree map[string]*routeNode // The route trie, keyed by domain. serveCache *gcache.Cache // Server caches for internal usage. routesMap map[string][]*HandlerItem // Route map mainly for route dumps and repeated route checks. statusHandlerMap map[string][]HandlerFunc // Custom status handler map. @@ -49,6 +50,24 @@ type ( registrar gsvc.Registrar // Registrar for service register. } + // routeNode is a node in the route prefix trie. + // Each node represents one URI segment separated by '/'. + // + // The trie structure replaces the original map[string]any design: + // - children: exact-match path segments (formerly map[string]any entries with string keys) + // - fuzz: wildcard child node for :param, {param}, *wildcard (formerly key "*fuzz") + // - list: handler items registered at this node (formerly key "*list" → *glist.List) + // + // Rules (preserved from the original implementation): + // - A fuzzy node MUST have a non-nil list; all sub-handlers accumulate here. + // - A leaf node MUST have a non-nil list. + // - Intermediate exact-match nodes have a nil list. + routeNode struct { + children map[string]*routeNode + fuzz *routeNode + list *glist.TList[*HandlerItem] + } + // Router object. Router struct { Uri string // URI. diff --git a/net/ghttp/ghttp_server.go b/net/ghttp/ghttp_server.go index a486c981cbb..27ae2e22c09 100644 --- a/net/ghttp/ghttp_server.go +++ b/net/ghttp/ghttp_server.go @@ -104,7 +104,7 @@ func GetServer(name ...any) *Server { closeChan: make(chan struct{}, 10000), serverCount: gtype.NewInt(), statusHandlerMap: make(map[string][]HandlerFunc), - serveTree: make(map[string]any), + serveTree: make(map[string]*routeNode), serveCache: gcache.New(), routesMap: make(map[string][]*HandlerItem), openapi: goai.New(), diff --git a/net/ghttp/ghttp_server_router.go b/net/ghttp/ghttp_server_router.go index 5be10aee174..866341eba80 100644 --- a/net/ghttp/ghttp_server_router.go +++ b/net/ghttp/ghttp_server_router.go @@ -30,6 +30,41 @@ var ( handlerIdGenerator = gtype.NewInt() ) +// ensureList lazily initializes the node's list if nil, appends it to dst and returns the result. +func (n *routeNode) ensureList(dst []*glist.TList[*HandlerItem]) []*glist.TList[*HandlerItem] { + if n.list == nil { + n.list = glist.NewT[*HandlerItem]() + } + return append(dst, n.list) +} + +// ensureFuzz lazily initializes the fuzz child node and returns it. +func (n *routeNode) ensureFuzz() *routeNode { + if n.fuzz == nil { + n.fuzz = &routeNode{} + } + return n.fuzz +} + +// ensureChild lazily initializes the named exact-match child node and returns it. +func (n *routeNode) ensureChild(part string) *routeNode { + if n.children == nil { + n.children = make(map[string]*routeNode) + } + if _, ok := n.children[part]; !ok { + n.children[part] = &routeNode{} + } + return n.children[part] +} + +// appendList appends the node's list to dst if it is non-nil, and returns the result. +func (n *routeNode) appendList(dst []*glist.TList[*HandlerItem]) []*glist.TList[*HandlerItem] { + if n.list != nil { + return append(dst, n.list) + } + return dst +} + // routerMapKey creates and returns a unique router key for given parameters. // This key is used for Server.routerMap attribute, which is mainly for checks for // repeated router registering. @@ -187,32 +222,32 @@ func (s *Server) doSetHandler( handler.Router.RegRule, handler.Router.RegNames = s.patternToRegular(uri) if _, ok := s.serveTree[domain]; !ok { - s.serveTree[domain] = make(map[string]any) + s.serveTree[domain] = &routeNode{} } // List array, very important for router registering. // There may be multiple lists adding into this array when searching from root to leaf. var ( array []string - lists = make([]*glist.List, 0) + lists []*glist.TList[*HandlerItem] ) if strings.EqualFold("/", uri) { array = []string{"/"} } else { array = strings.Split(uri[1:], "/") } - // Multilayer hash table: - // 1. Each node of the table is separated by URI path which is split by char '/'. - // 2. The key "*fuzz" specifies this node is a fuzzy node, which has no certain name. - // 3. The key "*list" is the list item of the node, MOST OF THE NODES HAVE THIS ITEM, - // especially the fuzzy node. NOTE THAT the fuzzy node must have the "*list" item, - // and the leaf node also has "*list" item. If the node is not a fuzzy node either - // a leaf, it neither has "*list" item. - // 2. The "*list" item is a list containing registered router items ordered by their + // Multilayer prefix trie: + // 1. Each node of the trie is separated by URI path which is split by char '/'. + // 2. The field "fuzz" specifies this node is a fuzzy node, which has no certain name. + // 3. The field "list" is the list item of the node, MOST OF THE NODES HAVE THIS ITEM, + // especially the fuzzy node. NOTE THAT the fuzzy node must have the "list" item, + // and the leaf node also has "list" item. If the node is not a fuzzy node either + // a leaf, it neither has "list" item. + // 4. The "list" item is a list containing registered router items ordered by their // priorities from high to low. If it's a fuzzy node, all the sub router items - // from this fuzzy node will also be added to its "*list" item. - // 3. There may be repeated router items in the router lists. The lists' priorities + // from this fuzzy node will also be added to its "list" item. + // 5. There may be repeated router items in the router lists. The lists' priorities // from root to leaf are from low to high. - var p = s.serveTree[domain] + p := s.serveTree[domain] for i, part := range array { // Ignore empty URI part, like: /user//index if part == "" { @@ -220,47 +255,28 @@ func (s *Server) doSetHandler( } // Check if it's a fuzzy node. if gregex.IsMatchString(`^[:\*]|\{[\w\.\-]+\}|\*`, part) { - part = "*fuzz" - // If it's a fuzzy node, it creates a "*list" item - which is a list - in the hash map. - // All the sub router items from this fuzzy node will also be added to its "*list" item. - if v, ok := p.(map[string]any)["*list"]; !ok { - newListForFuzzy := glist.New() - p.(map[string]any)["*list"] = newListForFuzzy - lists = append(lists, newListForFuzzy) - } else { - lists = append(lists, v.(*glist.List)) - } - } - // Make a new bucket for the current node. - if _, ok := p.(map[string]any)[part]; !ok { - p.(map[string]any)[part] = make(map[string]any) - } - // Loop to next bucket. - p = p.(map[string]any)[part] - // The leaf is a hash map and must have an item named "*list", which contains the router item. - // The leaf can be furthermore extended by adding more ket-value pairs into its map. - // Note that the `v != "*fuzz"` comparison is required as the list might be added in the former - // fuzzy checks. - if i == len(array)-1 && part != "*fuzz" { - if v, ok := p.(map[string]any)["*list"]; !ok { - leafList := glist.New() - p.(map[string]any)["*list"] = leafList - lists = append(lists, leafList) - } else { - lists = append(lists, v.(*glist.List)) + // If it's a fuzzy node, the CURRENT node p gets a list, which accumulates + // all sub-handler items from this fuzzy node onward. + lists = p.ensureList(lists) + // Navigate into the fuzz child node. + p = p.ensureFuzz() + } else { + // Make a new child bucket for the current segment if not exists, then navigate. + p = p.ensureChild(part) + // The leaf node must have a list item containing the router item. + if i == len(array)-1 { + lists = p.ensureList(lists) } } } // It iterates the list array of `lists`, compares priorities and inserts the new router item in // the proper position of each list. The priority of the list is ordered from high to low. - var item *HandlerItem for _, l := range lists { pushed := false for e := l.Front(); e != nil; e = e.Next() { - item = e.Value.(*HandlerItem) // Checks the priority whether inserting the route item before current item, // which means it has higher priority. - if s.compareRouterPriority(handler, item) { + if s.compareRouterPriority(handler, e.Value) { l.InsertBefore(e, handler) pushed = true goto end diff --git a/net/ghttp/ghttp_server_router_serve.go b/net/ghttp/ghttp_server_router_serve.go index 63cd8b2ef23..f2d9584bd91 100644 --- a/net/ghttp/ghttp_server_router_serve.go +++ b/net/ghttp/ghttp_server_router_serve.go @@ -132,35 +132,29 @@ func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*Han continue } // Make a list array with a capacity of 16. - lists := make([]*glist.List, 0, 16) + lists := make([]*glist.TList[*HandlerItem], 0, 16) for i, part := range array { // Add all lists of each node to the list array. - if v, ok := p.(map[string]any)["*list"]; ok { - lists = append(lists, v.(*glist.List)) - } - if v, ok := p.(map[string]any)[part]; ok { + lists = p.appendList(lists) + if child, ok := p.children[part]; ok { // Loop to the next node by certain key name. - p = v + p = child if i == len(array)-1 { - if v, ok := p.(map[string]any)["*list"]; ok { - lists = append(lists, v.(*glist.List)) - break - } + lists = p.appendList(lists) + break } - } else if v, ok := p.(map[string]any)["*fuzz"]; ok { + } else if p.fuzz != nil { // Loop to the next node by fuzzy node item. - p = v + p = p.fuzz } if i == len(array)-1 { // It here also checks the fuzzy item, // for rule case like: "/user/*action" matches to "/user". - if v, ok := p.(map[string]any)["*fuzz"]; ok { - p = v + if p.fuzz != nil { + p = p.fuzz } // The leaf must have a list item. It adds the list to the list array. - if v, ok := p.(map[string]any)["*list"]; ok { - lists = append(lists, v.(*glist.List)) - } + lists = p.appendList(lists) } } @@ -168,7 +162,7 @@ func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*Han // As the tail of the list array has the most priority, it iterates the list array from its tail to head. for i := len(lists) - 1; i >= 0; i-- { for e := lists[i].Front(); e != nil; e = e.Next() { - item := e.Value.(*HandlerItem) + item := e.Value // Filter repeated handler items, especially the middleware and hook handlers. // It is necessary, do not remove this checks logic unless you really know how it is necessary. //