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
|
// 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
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
|
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"
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user