From 0ed626e35169d31b56b2581c6e0fbe0665f34d14 Mon Sep 17 00:00:00 2001 From: Muyao CHEN Date: Mon, 16 Sep 2024 18:25:09 +0200 Subject: [PATCH] Route: Use a trie to register routes --- framework/core.go | 32 ++++++---- framework/trie.go | 154 ++++++++++++++++++++++++++++++++++++++++++++++ handlers.go | 25 ++++++++ routes.go | 12 +++- 4 files changed, 208 insertions(+), 15 deletions(-) create mode 100644 framework/trie.go diff --git a/framework/core.go b/framework/core.go index 1d56848..e260aeb 100644 --- a/framework/core.go +++ b/framework/core.go @@ -8,17 +8,17 @@ import ( // Core is the core struct of the framework type Core struct { - router map[string]map[string]ControllerHandler + router map[string]*Trie } // NewCore initialize the Core. func NewCore() *Core { - getRouter := map[string]ControllerHandler{} - postRouter := map[string]ControllerHandler{} - putRouter := map[string]ControllerHandler{} - deleteRouter := map[string]ControllerHandler{} + getRouter := NewTrie() + postRouter := NewTrie() + putRouter := NewTrie() + deleteRouter := NewTrie() - router := map[string]map[string]ControllerHandler{} + router := map[string]*Trie{} router["GET"] = getRouter router["POST"] = postRouter router["PUT"] = putRouter @@ -30,25 +30,33 @@ func NewCore() *Core { // Get is a simple get router func (c *Core) Get(url string, handler ControllerHandler) { upperUrl := strings.ToUpper(url) - c.router["GET"][upperUrl] = handler + if err := c.router["GET"].AddRouter(upperUrl, handler); err != nil{ + log.Println(err) + } } // Post is a simple post router func (c *Core) Post(url string, handler ControllerHandler) { upperUrl := strings.ToUpper(url) - c.router["POST"][upperUrl] = handler + if err := c.router["POST"].AddRouter(upperUrl, handler); err != nil{ + log.Println(err) + } } // Put is a simple put router func (c *Core) Put(url string, handler ControllerHandler) { upperUrl := strings.ToUpper(url) - c.router["PUT"][upperUrl] = handler + if err := c.router["PUT"].AddRouter(upperUrl, handler); err != nil{ + log.Println(err) + } } // Delete is a simple delete router func (c *Core) Delete(url string, handler ControllerHandler) { upperUrl := strings.ToUpper(url) - c.router["DELETE"][upperUrl] = handler + if err := c.router["DELETE"].AddRouter(upperUrl, handler); err != nil{ + log.Println(err) + } } // FindRouteByRequest finds route using the request @@ -62,8 +70,8 @@ func (c *Core) FindRouteByRequest(r *http.Request) ControllerHandler { return nil } - controller, ok := mapper[upperUri] - if !ok { + controller := mapper.FindRoute(upperUri) + if controller == nil { log.Printf("URI %q is not recognized\n", r.URL.Path) return nil } diff --git a/framework/trie.go b/framework/trie.go new file mode 100644 index 0000000..2b3d6fb --- /dev/null +++ b/framework/trie.go @@ -0,0 +1,154 @@ +package framework + +import ( + "errors" + "fmt" + "strings" +) + +type Trie struct { + root *node +} + +func NewTrie() *Trie { + return &Trie{root: newNode("")} +} + +func (t *Trie) FindRoute(uri string) ControllerHandler { + uri = strings.TrimPrefix(uri, "/") + if uri == "" { + return t.root.handler + } + + found := t.root.findRoute(uri) + if found == nil { + return nil + } + + return found.handler +} + +func (t *Trie) AddRouter(uri string, handler ControllerHandler) error { + uri = strings.TrimPrefix(uri, "/") + if uri == "" { + t.root.isLast = true + t.root.handler = handler + return nil + } + + upperUri := strings.ToUpper(uri) + match := t.FindRoute(upperUri) + if match != nil { + // existing route + return fmt.Errorf("existing route for %q", uri) + } + + // The route does not exist, add it to the tree + err := t.root.addRoute(upperUri, handler) + if err != nil { + return err + } + return nil +} + +type node struct { + isLast bool + segment string + handler ControllerHandler + children []*node +} + +func newNode(segment string) *node { + return &node{segment: segment} +} + +func isWildcard(s string) bool { + return strings.HasPrefix(s, ":") +} + +// /user/name +// /user/:id/name +// /user/3/name + +// findRoute finds the handler for the uri if exists. +// +// we suppose that uri passed here doesn't begin with "/" +func (n *node) findRoute(uri string) *node { + splitted := strings.SplitN(uri, "/", 2) + splittedLen := len(splitted) + + if isWildcard(splitted[0]) { + // input is a wildcard, check if this endpoint has already children + // if so, return the first one which is not nil. + nbChildren := len(n.children) + if nbChildren > 0 && n.children[0].segment != splitted[0] { + // several nodes exist, return the first one that is not nil + return n.children[0] + } + } + + // try to find the value in childre + for _, child := range n.children { + if isWildcard(child.segment) || child.segment == splitted[0] { + if splittedLen == 1 { + // This is the last value, do the check and return + if child.isLast { + // if isLast, that means we have already registered the endpoint + return child + } else { + // otherwise, take it as not registered + return nil + } + } + // More segments to check + return child.findRoute(splitted[1]) + } + } + + // nothing found in the children + return nil +} + +func (n *node) addRoute(uri string, handler ControllerHandler) error { + splitted := strings.SplitN(uri, "/", 2) + splittedLen := len(splitted) + isLast := splittedLen == 1 + + // try to find the value in childre + for _, child := range n.children { + if isWildcard(child.segment) || child.segment == splitted[0] { + if isLast { + // This is the last value, do the check and return + if child.isLast { + // if isLast, that means we have already registered the endpoint + return errors.New("node exists") + } else { + // otherwise, set the child + child.isLast = true + child.handler = handler + return nil + } + } + // More segments to check + return child.addRoute(splitted[1], handler) + } + } + + // create a new node + new := newNode(splitted[0]) + if isLast { + // this is the end + new.handler = handler + new.isLast = true + n.children = append(n.children, new) + return nil + } + // continue + new.isLast = false + err := new.addRoute(splitted[1], handler) + if err != nil { + return err + } + n.children = append(n.children, new) + return nil +} diff --git a/handlers.go b/handlers.go index d88bb41..d5bbc9c 100644 --- a/handlers.go +++ b/handlers.go @@ -56,3 +56,28 @@ func FooControllerHandler(ctx *framework.Context) error { return nil } + +func UserLoginController(ctx *framework.Context) error { + ctx.WriteJSON(http.StatusOK, "ok") + return nil +} + +func SubjectDelController(ctx *framework.Context) error { + ctx.WriteJSON(http.StatusAccepted, "deleted") + return nil +} + +func SubjectUpdateController(ctx *framework.Context) error { + ctx.WriteJSON(http.StatusAccepted, "updated") + return nil +} + +func SubjectGetController(ctx *framework.Context) error { + ctx.WriteJSON(http.StatusAccepted, "got") + return nil +} + +func SubjectListController(ctx *framework.Context) error { + ctx.WriteJSON(http.StatusAccepted, "list") + return nil +} diff --git a/routes.go b/routes.go index d698236..a28e611 100644 --- a/routes.go +++ b/routes.go @@ -3,7 +3,13 @@ package main import "git.vinchent.xyz/vinchent/go-web/framework" func registerRouter(core *framework.Core) { - core.Get("/foo", FooControllerHandler) - barGroup := core.Group("/bar") - barGroup.Get("/foo", FooControllerHandler) + core.Get("/user/login", UserLoginController) + + subjectApi := core.Group("/subject") + { + subjectApi.Delete("/:id", SubjectDelController) + subjectApi.Put("/:id", SubjectUpdateController) + subjectApi.Get("/:id", SubjectGetController) + subjectApi.Get("/list/all", SubjectListController) + } }