Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion net/ghttp/ghttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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.
Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion net/ghttp/ghttp_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
104 changes: 60 additions & 44 deletions net/ghttp/ghttp_server_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -187,80 +222,61 @@ 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.
Comment thread
LanceAdd marked this conversation as resolved.
// 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 == "" {
continue
}
// 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
Expand Down
30 changes: 12 additions & 18 deletions net/ghttp/ghttp_server_router_serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,43 +132,37 @@ 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)
}
}

// OK, let's loop the result list array, adding the handler item to the result handler result array.
// 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.
//
Expand Down
Loading