type node struct
在router.go中可以看到对node的使用
type Router struct {trees map[string]*node}
map中的key是不同的method,*node就是不同method的树的根节点
直接看结构体定义
type node struct {path string // 路径,比如`s`、`earch`、`up`wildChild bool // 标明当前节点的子是否为参数节点nType nodeType //maxParams uint8 // 自己以及所有子孙节点,path中的最大参数数量priority uint32 // 优先级,是当前节点的子孙节点数量+1,在介绍中有提到过indices string // 存放所有子节点的首字母,比如`eu`children []*node //handle Handle // 处理程序}
nodetype包含四种类型
type nodeType uint8const (static nodeType = iota // default,譬如search与sup中的s节点root // 每个方法的根节点param // 其他的节点catchAll // 存在*name的节点)
建树过程
建树主要涉及
node.addRoute()以及node.insertChild()两个函数
node.addRoute()
回顾一下,注册路由的函数是Router.Handle(),该函数调用node.addRoute(),其接收者是某个method的root节点。
// addRoute 向tree中增加节点// Not concurrency-safe!func (n *node) addRoute(path string, handle Handle) {fullPath := pathn.priority++ // 根节点的权重+1numParams := countParams(path) // 计算路径中的参数个数// non-empty treeif len(n.path) > 0 || len(n.children) > 0 {walk: // 循环的标记for {// Update maxParams of the current nodeif numParams > n.maxParams {n.maxParams = numParams}// 查找最长公共前缀// This also implies that the common prefix contains no ':' or '*'// since the existing key can't contain those chars.i := 0max := min(len(path), len(n.path)) // 最长不会超过最短path的长度for i < max && path[i] == n.path[i] {i++}// Split edge// 如果公共部分比当前节点的path要短,如sup与search// 需要将原来的node切分成公共前缀node和后续的node两个部分if i < len(n.path) {child := node{path: n.path[i:],wildChild: n.wildChild,nType: static,indices: n.indices,children: n.children,handle: n.handle,priority: n.priority - 1, // 存疑,不知道为什么要-1}// Update maxParams (max of all children)for i := range child.children {if child.children[i].maxParams > child.maxParams {child.maxParams = child.children[i].maxParams}}n.children = []*node{&child}// []byte for proper unicode char conversion, see #65n.indices = string([]byte{n.path[i]})n.path = path[:i]n.handle = nil // 由于已经将handle给了子节点,当前路径中不含通配符// 很明显的,如果有正常的子节点,就不可能再有`:`或者`*`,n.wildChild = false}// Make new node a child of this node// 如果公共部分比新的path要短// 由于是先处理的n节点,所以当前节点中存放的就是公共前缀// 所以只需要将path的非公共部分存放到一个新的节点中if i < len(path) {path = path[i:]// 这个条件判断,当前路径是否有名字参数// 也可以直接理解为,没有经过上一个if的处理// 因为上一个if将n.wildChild设为了false// 所以也意味着,公共前缀的长度>=len(n.path)if n.wildChild {n = n.children[0]n.priority++// Update maxParams of the child nodeif numParams > n.maxParams {n.maxParams = numParams}numParams--// Check if the wildcard matchesif len(path) >= len(n.path) && n.path == path[:len(n.path)] &&// Adding a child to a catchAll is not possiblen.nType != catchAll &&// Check for longer wildcard, e.g. :name and :names(len(n.path) >= len(path) || path[len(n.path)] == '/') {continue walk} else {// Wildcard conflictvar pathSeg stringif n.nType == catchAll {pathSeg = path} else {pathSeg = strings.SplitN(path, "/", 2)[0]}prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.pathpanic("'" + pathSeg +"' in new path '" + fullPath +"' conflicts with existing wildcard '" + n.path +"' in existing prefix '" + prefix +"'")}}c := path[0]// slash after paramif n.nType == param && c == '/' && len(n.children) == 1 {n = n.children[0]n.priority++continue walk}// Check if a child with the next path byte existsfor i := 0; i < len(n.indices); i++ {if c == n.indices[i] {i = n.incrementChildPrio(i)n = n.children[i]continue walk}}// Otherwise insert itif c != ':' && c != '*' {// []byte for proper unicode char conversion, see #65n.indices += string([]byte{c})child := &node{maxParams: numParams,}n.children = append(n.children, child)n.incrementChildPrio(len(n.indices) - 1)n = child}n.insertChild(numParams, path, fullPath, handle)return} else if i == len(path) { // Make node a (in-path) leaf// 新注册的路由路径与当前节点路径相同// 想要注册的路由是否已有handlerif n.handle != nil {panic("a handle is already registered for path '" + fullPath + "'")}n.handle = handle}return}} else { // Empty treen.insertChild(numParams, path, fullPath, handle) //空树则给该树插入子节点n.nType = root // 标明当前节点类型}}
node.insertChild()
func (n *node) insertChild(numParams uint8, path, fullPath string, handle Handle) {var offset int // 用于记录目前已经处理了path中的长度// find prefix until first wildcard (beginning with ':'' or '*'')// 如果存在`:`或者`*`,需要对节点进行特殊处理for i, max := 0, len(path); numParams > 0; i++ {c := path[i]if c != ':' && c != '*' {continue}// find wildcard end (either '/' or path end)// 找到`:`或`*`以后,找到其结尾end := i + 1for end < max && path[end] != '/' {switch path[end] {// the wildcard name must not contain ':' and '*'case ':', '*':panic("only one wildcard per path segment is allowed, has: '" +path[i:] + "' in path '" + fullPath + "'")default:end++}}// 如果存在子节点,则在此插入通配符将导致子节点无法访问// 比如存在`/path1/path2`,则无法插入`/path1/:post`if len(n.children) > 0 {panic("wildcard route '" + path[i:end] +"' conflicts with existing children in path '" + fullPath + "'")}// check if the wildcard has a name// `:`和`*`是需要有名字的if end-i < 2 {panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")}if c == ':' { // param// 将`:`之前的分割出来if i > 0 {n.path = path[offset:i]offset = i}child := &node{nType: param,maxParams: numParams,}n.children = []*node{child}n.wildChild = truen = child // 移动到child节点,准备处理剩下的pathn.priority++numParams-- // 处理掉一个通配符,剩下的路径中的参数个数--// if the path doesn't end with the wildcard, then there// will be another non-wildcard subpath starting with '/'// 如果通配符项不是剩余path的全部路径,则需要将通配符这一节// 单独做成一个节点if end < max {n.path = path[offset:end]offset = endchild := &node{maxParams: numParams,priority: 1,}n.children = []*node{child}n = child}} else { // catchAllif end != max || numParams > 1 {panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")}if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")}// currently fixed width 1 for '/'i--if path[i] != '/' {panic("no / before catch-all in path '" + fullPath + "'")}n.path = path[offset:i]// first node: catchAll node with empty pathchild := &node{wildChild: true,nType: catchAll,maxParams: 1,}// update maxParams of the parent nodeif n.maxParams < 1 {n.maxParams = 1}n.children = []*node{child}n.indices = string(path[i])n = childn.priority++// second node: node holding the variablechild = &node{path: path[i:],nType: catchAll,maxParams: 1,handle: handle,priority: 1,}n.children = []*node{child}return}}// insert remaining path part and handle to the leafn.path = path[offset:] // 节点中的path只保留非公共部分n.handle = handle}
其他函数
func countParams()
用于计算一个路径中有多少占位参数(:name和*name)
func countParams(path string) uint8 {var n uintfor i := 0; i < len(path); i++ {if path[i] != ':' && path[i] != '*' {continue}n++}if n >= uint(maxParamCount) {return maxParamCount}return uint8(n)}// var maxParamCount uint8 = ^uint8(0) 也就是255
相关内容
- #65,关于string的类型转换问题
