Router 目标
使用 Trie 树实现动态路由(dynamic route)解析。
支持两种模式:name
和*filepath
。
Trie树
trie.go 每个节点结构体信息
1 2 3 4 5 6 type node struct { pattern string part string children []*node isWild bool }
匹配函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func (n *node) matchChild(part string ) *node { for _, child := range n.children { if child.part == part || child.isWild { return child } } return nil } 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 }
插入和查找函数
插入:递归查找每一层的节点,如果没有匹配到当前part
的节点,则新建一个。
查找:递归查询每一层的节点,退出规则是,匹配到了*
,匹配失败,或者匹配到了第len(parts)
层节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 func (n *node) insert(pattern string , parts []string , height int ) { if len (parts) == height { n.pattern = pattern return } 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, height+1 ) } 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, height+1 ) if result != nil { return result } } return nil }
Router路由 将Trie树应用到路由中
roots存储每种请求方式的Trie树的根节点
handlers存储每种请求方式的HandlerFunc
1 2 3 4 type router struct { roots map [string ]*node handlers map [string ]HandlerFunc }
router.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 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 } 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 } 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 } 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 } func (r *router) handle(c *Context) { n, params := r.getRoute(c.Method, c.Path) if n != nil { c.Params = params key := c.Method + "-" + n.pattern r.handlers[key](c) } else { c.String(http.StatusNotFound, "404 NOT FOUND: %s\n" , c.Path) } }
context.go 需要对 Context 对象增加一个属性和方法,来提供对路由参数的访问。我们将解析后的参数存储到Params
中,通过c.Param("lang")
的方式获取到对应的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type Context struct { Writer http.ResponseWriter Req *http.Request Path string Method string Params map [string ]string StatusCode int } func (c *Context) Param(key string ) string { value, _ := c.Params[key] return value }
main.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func main () { r := gee.New() r.GET("/" , func (c *gee.Context) { c.HTML(http.StatusOK, "<h1>Hello Gee</h1>" ) }) r.GET("/hello" , func (c *gee.Context) { c.String(http.StatusOK, "hello %s, you're at %s\n" , c.Query("name" ), c.Path) }) r.GET("/hello/:name" , func (c *gee.Context) { c.String(http.StatusOK, "hello %s, you're at %s\n" , c.Param("name" ), c.Path) }) r.GET("/assets/*filepath" , func (c *gee.Context) { c.JSON(http.StatusOK, gee.H{"filepath" : c.Param("filepath" )}) }) r.Run(":9999" ) }