Route: Use a trie to register routes
This commit is contained in:
parent
d90e564620
commit
0ed626e351
@ -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
|
||||
}
|
||||
|
154
framework/trie.go
Normal file
154
framework/trie.go
Normal 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
|
||||
}
|
25
handlers.go
25
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
|
||||
}
|
||||
|
12
routes.go
12
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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user