Route: Use a trie to register routes

This commit is contained in:
Muyao CHEN 2024-09-16 18:25:09 +02:00
parent d90e564620
commit 0ed626e351
4 changed files with 208 additions and 15 deletions

View File

@ -8,17 +8,17 @@ import (
// Core is the core struct of the framework // Core is the core struct of the framework
type Core struct { type Core struct {
router map[string]map[string]ControllerHandler router map[string]*Trie
} }
// NewCore initialize the Core. // NewCore initialize the Core.
func NewCore() *Core { func NewCore() *Core {
getRouter := map[string]ControllerHandler{} getRouter := NewTrie()
postRouter := map[string]ControllerHandler{} postRouter := NewTrie()
putRouter := map[string]ControllerHandler{} putRouter := NewTrie()
deleteRouter := map[string]ControllerHandler{} deleteRouter := NewTrie()
router := map[string]map[string]ControllerHandler{} router := map[string]*Trie{}
router["GET"] = getRouter router["GET"] = getRouter
router["POST"] = postRouter router["POST"] = postRouter
router["PUT"] = putRouter router["PUT"] = putRouter
@ -30,25 +30,33 @@ func NewCore() *Core {
// Get is a simple get router // Get is a simple get router
func (c *Core) Get(url string, handler ControllerHandler) { func (c *Core) Get(url string, handler ControllerHandler) {
upperUrl := strings.ToUpper(url) 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 // Post is a simple post router
func (c *Core) Post(url string, handler ControllerHandler) { func (c *Core) Post(url string, handler ControllerHandler) {
upperUrl := strings.ToUpper(url) 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 // Put is a simple put router
func (c *Core) Put(url string, handler ControllerHandler) { func (c *Core) Put(url string, handler ControllerHandler) {
upperUrl := strings.ToUpper(url) 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 // Delete is a simple delete router
func (c *Core) Delete(url string, handler ControllerHandler) { func (c *Core) Delete(url string, handler ControllerHandler) {
upperUrl := strings.ToUpper(url) 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 // FindRouteByRequest finds route using the request
@ -62,8 +70,8 @@ func (c *Core) FindRouteByRequest(r *http.Request) ControllerHandler {
return nil return nil
} }
controller, ok := mapper[upperUri] controller := mapper.FindRoute(upperUri)
if !ok { if controller == nil {
log.Printf("URI %q is not recognized\n", r.URL.Path) log.Printf("URI %q is not recognized\n", r.URL.Path)
return nil return nil
} }

154
framework/trie.go Normal file
View File

@ -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
}

View File

@ -56,3 +56,28 @@ func FooControllerHandler(ctx *framework.Context) error {
return nil 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
}

View File

@ -3,7 +3,13 @@ package main
import "git.vinchent.xyz/vinchent/go-web/framework" import "git.vinchent.xyz/vinchent/go-web/framework"
func registerRouter(core *framework.Core) { func registerRouter(core *framework.Core) {
core.Get("/foo", FooControllerHandler) core.Get("/user/login", UserLoginController)
barGroup := core.Group("/bar")
barGroup.Get("/foo", FooControllerHandler) subjectApi := core.Group("/subject")
{
subjectApi.Delete("/:id", SubjectDelController)
subjectApi.Put("/:id", SubjectUpdateController)
subjectApi.Get("/:id", SubjectGetController)
subjectApi.Get("/list/all", SubjectListController)
}
} }