Gin 框架 核心 httprouter tree树结构解析

gin web 是一个 go 的开源框架 他在保持简洁小巧的设计 的同时又保持了不错的性能 着其中也得益于 他在解析路由的时候用到了 httprouter 这个开源的路由解析框架 gin框架将get set 请求分配到不同的rpouter 数上进行解析。

type methodTree struct {
	//GET SET 
	method string //GET SET 方法
	root   *node //路由tree
}

以下是树的结构

//节点类型
const (
	static nodeType = iota // default //静态节点 比如这个节点只是 分离出来的 本身并不承载路由 当两个节点有公共的前缀的时候 就会分裂 出一个静态节点
	root //第一个节点 所有节点的父节点
	param//普通的带有处理器的节点
	catchAll//模糊匹配的节点
)
	
type node struct {
	path      string // 例如: /a 
	indices   string //分裂分支的第一个字符 假设 /abc 和 /adb 那么 此时 就会是 bd
	children  []*node //子节点
	handlers  HandlersChain //请求处理器
	priority  uint32 //优先级 
	nType     nodeType //节点类型 
	maxParams uint8 //路径上的最大个参数
	wildChild bool //是否模糊节点
	fullPath  string //全路径
}

Gin框架中的 GET PUT 都会绑定不同的路有树node 来处理路由请求


//取最长公共前缀
func longestCommonPrefix(a, b string) int {
	i := 0
	max := min(len(a), len(b))
	for i < max && a[i] == b[i] {
		i++
	}
	return i
}

// Search for a wildcard segment and check the name for invalid characters.
// Returns -1 as index, if no wildcard was found.
//返回 通配符 假设 test:tongpeifu 那么 wilcard 就是 tongpeifu  i 返回通配符开始的索引 valid 返回 比如caomao:1233:是否符合规范
// 这样就是不对的
func findWildcard(path string) (wilcard string, i int, valid bool) {
	// Find start
	for start, c := range []byte(path) {
		// A wildcard starts with ':' (param) or '*' (catch-all)
		//如果 不是以 : 或者 * 开头的 字符 则 返回 -1 false
		if c != ':' && c != '*' {
			continue
		}

		// Find end and check for invalid characters
		valid = true
		// 寻找 通配符 满足上面条件 以 : 或 * 开头 循环遍历下面的字符
		//如果遇到:asdasdad 或者 :12312321* 这样的结尾 代表最后一级菜单 开始结束
		//如果遇到:asdasd/ 或者*sadsad/ 这样的 就要截断 到/之前 部分
		for end, c := range []byte(path[start+1:]) {
			switch c {
			case '/':
				return path[start : start+1+end], start, valid
				//如果遇到 *123123* :12313*  *213123:  :213123:  这样的 就通不过验证
			case ':', '*':
				valid = false
			}
		}

		return path[start:], start, valid
	}
	//如果没有找到 在如 /adasd 这样的 路由里面 没找到 * 或者 :这样的通配符 就直接返回
	return "", -1, false
}

func countParams(path string) uint16 {
	var n uint
	for i := range []byte(path) {
		switch path[i] {
		case ':', '*':
			n++
		}
	}
	return uint16(n)
}

type nodeType uint8

const (
	static nodeType = iota // default
	root
	param
	catchAll
)

type node struct {
	path      string
	indices   string
	wildChild bool
	nType     nodeType
	priority  uint32
	children  []*node
	handle    Handle
}

// 并不影响 核心功能 但是 可以加快 孩子节点 查找速度 对应priority 字段 优先级越高
//那么子节点数量就越多 那么把 调整下 数组索引 使它比优先级小的索引 更小 数组在循环的时候遍历
//的速度就变快了
func (n *node) incrementChildPrio(pos int) int {
	cs := n.children
	cs[pos].priority++
	prio := cs[pos].priority

	// Adjust position (move to front)
	newPos := pos
	for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {
		// Swap node positions
		cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]

	}

	// Build new index char string
	//对子节点进行排序后需要 把 indices这个字段同时更新下 因为节点位置 发生了交换
	// 比如 原来 /a childrens[ /basd /ca /dew /ecs ] indices = 各孩子首字母 = bcde  => [ /ca   /basd  /dew /ecs  ]  = cbde
	//
	if newPos != pos {
		n.indices = n.indices[:newPos] + // Unchanged prefix, might be empty
			n.indices[pos:pos+1] + // The index char we move
			n.indices[newPos:pos] + n.indices[pos+1:] // Rest without char at 'pos'
	}

	return newPos
}

// addRoute adds a node with the given handle to the path.
// Not concurrency-safe!
func (n *node) addRoute(path string, handle Handle) {
	fullPath := path
	n.priority++

	// Empty tree
	//当树是没有被初始化时
	if len(n.path) == 0 && len(n.indices) == 0 {
		n.insertChild(path, fullPath, handle)
		n.nType = root
		return
	}

walk:
	for {
		// Find the longest common prefix.
		// This also implies that the common prefix contains no ':' or '*'
		// since the existing key can't contain those chars.
		//最长公共子头
		i := longestCommonPrefix(path, n.path)

		// Split edge
		//因为 包含公共前缀 所以需要 分割
		//这里要考虑两种情况 假设 先有n.path =  /a 再有 path =  /abc 那么 i = len(取公共前缀("/a","/abc")) = 2 len(n.path) =2 那么 不会产生分裂节点
		//第二种情况 假设 先有n.path = /abc 再有 path = /a 那么 i = len(取公共前缀("/a","/abc")) = 2  len(n.path) = 4 此时 i< len(n.path) 那么便会产生分裂的节点
		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
			}

			//上面这段代码 假设已 有 路由/abc 如果这时添加了一个 路由/asd 那么 就会把 /abc 这个节点的
			//信息拷贝出来 然后传建一个新的 节点并且 节点的路径 是 bc


			//把 当前节点的子节点赋值为 新创建出来的 复制了原有节点信息的节点
			n.children = []*node{&child}

			// 记录分裂节点的 第一个字 /abc /asd  => b
			n.indices = string([]byte{n.path[i]})


			//节点的路劲 变为 /a
			n.path = path[:i]

			//由于静态节点不存在 处理器 所以 nil
			n.handle = nil

			//静态节点 不是模糊匹配节点
			n.wildChild = false
		}

		//这里当他们 不能分裂出静态节点时 这里假设/ 本身 不会分裂
		//当 n.path = /abc path = /a 时 i = 2 有公共前缀 此时 i = len(path)
		//当 n.path = /a path = /abc 时 i = 2 有公共前缀 此时 i < len(path)
		//当 n.path = /asd path = /bcx 时 其实和上面一种情况是一样的 i = 1 此时 i < len(path)
		// Make new node a child of this node
		if i < len(path) {
			//取出节点的不包含公共前缀的部分
			path = path[i:]
			//如果是* 这样模糊匹配的节点
			
			if n.wildChild {
				//取出第一个孩子节点
				n = n.children[0]
				//优先级加1
				n.priority++
				// Check if the wildcard matches
				//如果 加入的path 长度 大于 本节点 的长度 并且 本节点 path 和 path路径 符合
				if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
					// Adding a child to a catchAll is not possible
					//增加的是 模糊匹配的节点
					n.nType != catchAll &&
					// Check for longer wildcard, e.g. :name and :names
					//模糊匹配节点 或者带 /的节点
					(len(n.path) >= len(path) || path[len(n.path)] == '/') {
					continue walk
				} else {
					// Wildcard conflict
					pathSeg := path
					if n.nType != catchAll {
						pathSeg = strings.SplitN(pathSeg, "/", 2)[0]
					}
					prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
					panic("'" + pathSeg +
						"' in new path '" + fullPath +
						"' conflicts with existing wildcard '" + n.path +
						"' in existing prefix '" + prefix +
						"'")
				}
			}
			//取出第一个字符
			idxc := path[0]

			//  查找 '/'  节点下 第一个节点
			//如果当前节点是 param 类型节点 并且 第一个字符是 "/" 并且 只有一个子节点
			if n.nType == param && idxc == '/' && len(n.children) == 1 {
				//当前节点等于 子节点
				n = n.children[0]
				//优先级加1
				n.priority++
				//跳转到walk
				continue walk
			}

			// Check if a child with the next path byte exists
			//循环查找 如果记录的子节点的第一个 字符 和当前要插入节点 的 以一个字符相符
			for i, c := range []byte(n.indices) {
				if c == idxc {
					//优先级排序
					i = n.incrementChildPrio(i)
					//当前节点为 子节点
					n = n.children[i]
					//跳转walk
					continue walk
				}
			}

			// 如果 添加的 节点 既不是 * 也不是: 这样的 通配 节点 就执行插入
			if idxc != ':' && idxc != '*' {
				// []byte for proper unicode char conversion, see #65
				//把药加入的节点首字符添加进当前节点
				n.indices += string([]byte{idxc})
				child := &node{}
				//添加孩子节点
				n.children = append(n.children, child)
				//刚加入的节点 优先级 +1
				n.incrementChildPrio(len(n.indices) - 1)
				//当前节点更新为 新节点
				n = child
			}

			//添加节点
			n.insertChild(path, fullPath, handle)
			return
		}

		// 如果当前节点 一斤有了一个处理函数 那么就报错
		if n.handle != nil {
			panic("a handle is already registered for path '" + fullPath + "'")
		}
		// 添加 处理函数
		n.handle = handle
		return
	}
}

func (n *node) insertChild(path, fullPath string, handle Handle) {
	for {
		// 找到通配符 并返回 通配符
		wildcard, i, valid := findWildcard(path)
		//如果没有 通配符 就是跳出这个 for 循环
		if i < 0 { // No wilcard found
			break
		}

		// The wildcard name must not contain ':' and '*'
		//找到的 含有通配符的router 是否 符合 规则 :123123: 类似这样的就会报错
		if !valid {
			panic("only one wildcard per path segment is allowed, has: '" +
				wildcard + "' in path '" + fullPath + "'")
		}
		//通配符 必须要有名字 如果只是 : * 的话 那没名字也会报错  必须至少带有一个后缀 如 : *d :a
		// Check if the wildcard has a name
		if len(wildcard) < 2 {
			panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
		}

		// Check if this node has existing children which would be
		// unreachable if we insert the wildcard here
		//如果这个节点已经有子节点了 就不能插入了
		if len(n.children) > 0 {
			panic("wildcard segment '" + wildcard +
				"' conflicts with existing children in path '" + fullPath + "'")
		}

		//这里处理 通配符 是 :的情况
		if wildcard[0] == ':' { // param
			// 对于 a:xxx 这样 要提取 n.path = a  path = :xxx
			if i > 0 {
				// Insert prefix before the current wildcard
				n.path = path[:i]
				path = path[i:]
			}
			n.wildChild = true
			//初始化 子节点 param类型 是通配符类型
			child := &node{
				nType: param,
				path:  wildcard,
			}
			//添加子节点
			n.children = []*node{child}
			//当前节点 置为子节点
			n = child
			//赋值优先级
			n.priority++

			// If the path doesn't end with the wildcard, then there
			// will be another non-wildcard subpath starting with '/'
			//当 path = :tool 时 那么 通配符 和 :tool 事相时的 那么就结束了
			//如果不相等说明 后面 还没结束 :tool/12323
			if len(wildcard) < len(path) {
				//path去除掉 已经添加的部分
				path = path[len(wildcard):]
				//下面是新建一个节点再循环 上面的步骤
				child := &node{
					priority: 1,
				}
				n.children = []*node{child}
				n = child
				continue
			}

			// Otherwise we're done. Insert the handle in the new leaf
			n.handle = handle
			return

		} else { // catchAll
			// path 以 *sadsad/123结尾 表示还没结束 *只能出现在结尾 asd/123*   asd/12*3 这样
			if i+len(wildcard) != len(path) {
				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[:i]
			//匹配路由
			// First node: catchAll node with empty path
			child := &node{
				wildChild: true,
				nType:     catchAll,
			}
			n.children = []*node{child}
			n.indices = string('/')
			n = child
			n.priority++
				
			//存放变量	
			// Second node: node holding the variable
			child = &node{
				path:     path[i:],
				nType:    catchAll,
				handle:   handle,
				priority: 1,
			}
			n.children = []*node{child}

			return
		}
	}

	// If no wildcard was found, simply insert the path and handle
	n.path = path
	n.handle = handle
}

发布了67 篇原创文章 · 获赞 5 · 访问量 3159

猜你喜欢

转载自blog.csdn.net/weixin_41315492/article/details/103905721
今日推荐