成都网站开发企业,刷业务网站怎么做,番禺网站开发公司,做优秀网站学习Go语言Web框架Gee总结--前缀树路由Router router/gee/trie.gorouter/gee/router.gorouter/gee/context.gorouter/main.go 学习网站来源#xff1a;Gee
项目目录结构#xff1a;
router/gee/trie.go
实现动态路由最常用的数据结构#xff0c;被称为前缀树(Trie树)
关… 学习Go语言Web框架Gee总结--前缀树路由Router router/gee/trie.gorouter/gee/router.gorouter/gee/context.gorouter/main.go 学习网站来源Gee
项目目录结构
router/gee/trie.go
实现动态路由最常用的数据结构被称为前缀树(Trie树)
关于前缀树
根节点不包含字符除根节点外每一个节点都只包含一个字符从根节点到某一节点路径上经过的字符连接起来为该节点对应的字符串每个节点的所有子节点包含的字符都不相同
package geeimport (fmtstrings
)//node结构体表示路由节点包含了待匹配的路由pattern、路由中的一部分part、子节点children和是否精确匹配isWild
type node struct {pattern string // 待匹配路由例如 /p/:langpart string // 路由中的一部分例如 :langchildren []*node // 子节点例如 [doc, tutorial, intro]isWild bool // 是否精确匹配part 含有 : 或 * 时为true
}/*
用于将节点的信息格式化为字符串的方法
该方法返回一个包含节点模式pattern、节点部分part和是否是通配符isWild的字符串
其中%t是用于格式化布尔值的占位符
例如如果有一个节点n它的pattern为/user/:idpart为:idisWild为true那么调用n.String()会返回以下格式的字符串:
node{pattern/user/:id, part:id, isWildtrue}
*/
func (n *node) String() string {return fmt.Sprintf(node{pattern%s, part%s, isWild%t}, n.pattern, n.part, n.isWild)
}/*
用于遍历路由树中的节点并将具有路由模式的节点添加到传入的列表中
方法接收一个指向节点切片的指针作为参数然后遍历当前节点及其子节点将具有路由模式的节点添加到传入的节点切片中
首先如果当前节点的pattern不为空即具有路由模式则将当前节点添加到传入的节点切片中。
然后遍历当前节点的子节点对每个子节点递归调用travel方法将子节点及其子节点的路由模式节点添加到传入的节点切片中
*/
func (n *node) travel(list *[]*node) {if n.pattern ! {*list append(*list, n)}for _, child : range n.children {child.travel(list)}
}/*
用于在子节点中找到第一个匹配成功的节点如果子节点的part和传入的part相等或者子节点是通配符isWild为true
则返回该子节点。如果没有匹配成功的子节点则返回nil
*/
func (n *node) matchChild(part string) *node {for _, child : range n.children {if child.part part || child.isWild {return child}}return nil
}/*
用于在子节点中找到所有匹配成功的节点如果子节点的part和传入的part相等或者子节点是通配符isWild为true
则将该子节点加入到nodes切片中。最后返回nodes切片其中包含了所有匹配成功的子节点
*/
func (n *node) matchChildren(part string) []*node {nodes : make([]*node, 0)for _, child : range n.children {if child.part part || child.isWild {nodes append(nodes, child)}}return nodes
}/*
用于向路由树中插入路由。它接收三个参数pattern表示要插入的路由模式parts表示路由模式分割后的部分height表示当前插入的层级
首先它检查当前层级是否已经是最后一层即parts的长度是否等于height如果是则将当前节点的pattern设置为传入的pattern表示找到了对应的路由
否则它从parts中取出当前层级的部分然后调用matchChild方法查找是否已经存在对应的子节点
如果没有找到对应的子节点说明需要创建一个新的子节点然后将其插入到当前节点的children中
然后递归调用insert方法将pattern、parts和height1传入新创建的子节点中继续插入下一层级的路由
*/
func (n *node) insert(pattern string, parts []string, height int) {if len(parts) height {n.pattern patternreturn}part : parts[height]child : n.matchChild(part)if child nil {child node{part: part, isWild: part[0] : || part[0] *}n.children append(n.children, child)}child.insert(pattern, parts, height1)
}/*
用于在路由树中搜索路由。它接收两个参数parts表示路由模式分割后的部分height表示当前搜索的层级
首先它检查当前层级是否已经是最后一层即parts的长度是否等于height或者当前节点是一个通配符节点part以*开头
如果是则检查当前节点是否有对应的路由模式如果有则返回当前节点表示找到了对应的路由
否则它从parts中取出当前层级的部分然后调用matchChildren方法查找所有匹配的子节点
然后对于每一个匹配的子节点递归调用search方法将parts和height1传入子节点中继续搜索下一层级的路由
如果在递归过程中找到了对应的路由节点则直接返回该节点如果没有找到则返回nil
*/
func (n *node) search(parts []string, height int) *node {if len(parts) height || strings.HasPrefix(n.part, *) {if n.pattern {return nil}return n}part : parts[height]children : n.matchChildren(part)for _, child : range children {result : child.search(parts, height1)if result ! nil {return result}}return nil
}
router/gee/router.go Trie 树的插入与查找都成功实现了接下来我们将 Trie 树应用到路由中去吧。我们使用 roots 来存储每种请求方式的Trie 树根节点。使用 handlers 存储每种请求方式的 HandlerFunc 。getRoute 函数中还解析了:和*两种匹配符的参数返回一个 map package geeimport (net/httpstrings
)//用于存储路由树和处理函数
type router struct {roots map[string]*nodehandlers map[string]HandlerFunc
}func newRouter() *router {return router{roots: make(map[string]*node),handlers: make(map[string]HandlerFunc),}
}//用于解析路由模式的函数
func parsePattern(pattern string) []string {vs : strings.Split(pattern, /)parts : make([]string, 0)for _, item : range vs {if item ! {parts append(parts, item)if item[0] * {break}}}return parts
}/*
用于向路由器中添加路由
首先调用 parsePattern 函数解析路由模式然后将方法和模式拼接成键并检查 roots 中是否存在对应的方法
如果不存在则将方法添加到 roots 中然后调用 insert 方法将模式插入到路由树中并将处理函数添加到 handlers 中
*/
func (r *router) addRoute(method string, pattern string, handler HandlerFunc) {parts : parsePattern(pattern)key : method - pattern_, ok : r.roots[method]if !ok {r.roots[method] node{}}r.roots[method].insert(pattern, parts, 0)r.handlers[key] handler
}/*
用于根据请求方法和路径获取路由
首先调用 parsePattern 函数解析路径然后检查 roots 中是否存在对应的方法
如果存在则调用 search 方法在路由树中查找匹配的节点并将匹配的部分和参数存储到 params 中
最后返回匹配的节点和参数
*/
func (r *router) getRoute(method string, path string) (*node, map[string]string) {searchParts : parsePattern(path)params : make(map[string]string)root, ok : r.roots[method]if !ok {return nil, nil}n : root.search(searchParts, 0)if n ! nil {parts : parsePattern(n.pattern)for index, part : range parts {if part[0] : {params[part[1:]] searchParts[index]}if part[0] * len(part) 1 {params[part[1:]] strings.Join(searchParts[index:], /)break}}return n, params}return nil, nil
}/*
用于获取指定请求方法下的所有路由节点
首先检查 roots 中是否存在对应的方法
如果存在则调用 travel 方法遍历路由树并将节点存储到切片中最后返回该切片
*/
func (r *router) getRoutes(method string) []*node {root, ok : r.roots[method]if !ok {return nil}nodes : make([]*node, 0)root.travel(nodes)return nodes
}/*
用于处理请求
首先调用 getRoute 方法获取匹配的路由节点和参数
然后根据匹配的节点和请求方法拼接键从 handlers 中取出对应的处理函数并调用它
如果找不到匹配的路由节点则返回 404 NOT FOUND
*/
func (r *router) handle(c *Context) {n, params : r.getRoute(c.Method, c.Path)if n ! nil {c.Params paramskey : c.Method - n.patternr.handlers[key](c)} else {c.String(http.StatusNotFound, 404 NOT FOUND: %s\n, c.Path)}
}
router/gee/context.go
由于需要能够访问到解析的参数需要对context对象增加关于param的属性与方法
type Context struct {// origin objectsWriter http.ResponseWriterReq *http.Request// request infoPath stringMethod stringParams map[string]string// response infoStatusCode int
}func (c *Context) Param(key string) string {value, _ : c.Params[key]return value
}router/main.go
package main
import (net/httpgee
)func main() {r : gee.New()r.GET(/, func(c *gee.Context) {c.HTML(http.StatusOK, h1Hello Gee/h1)})r.GET(/hello, func(c *gee.Context) {c.String(http.StatusOK, hello %s, youre at %s\n, c.Query(name), c.Path)})r.GET(/hello/:name, func(c *gee.Context) {c.String(http.StatusOK, hello %s, youre at %s\n, c.Param(name), c.Path)})//添加了一个带有通配符的路由//当用户访问 /assets/some/file/path 路径时会执行传入的匿名函数该函数从路径参数中获取 filepath 的值//并将其包含在 JSON 响应中返回r.GET(/assets/*filepath, func(c *gee.Context) {c.JSON(http.StatusOK, gee.H{filepath: c.Param(filepath)})})r.Run(:9999)
}这样动态路由就成功实现了