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 那么 此时 就会是 bdchildren []*node //子节点handlers HandlersChain //请求处理器priority uint32 //优先级 nType nodeType //节点类型 maxParams uint8 //路径上的最大个参数wildChild bool //是否模糊节点fullPath string //全路径
}
Gin框架中的 GET PUT 都会绑定不同的路有树node 来处理路由请求
//取最长公共前缀
func longestCommonPrefix(a, b string) int {i := 0max := 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 startfor start, c := range []byte(path) {// A wildcard starts with ':' (param) or '*' (catch-all)//如果 不是以 : 或者 * 开头的 字符 则 返回 -1 falseif c != ':' && c != '*' {continue}// Find end and check for invalid charactersvalid = 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 uintfor i := range []byte(path) {switch path[i] {case ':', '*':n++}}return uint16(n)
}type nodeType uint8const (static nodeType = iota // defaultrootparamcatchAll
)type node struct {path stringindices stringwildChild boolnType nodeTypepriority uint32children []*nodehandle Handle
}// 并不影响 核心功能 但是 可以加快 孩子节点 查找速度 对应priority 字段 优先级越高
//那么子节点数量就越多 那么把 调整下 数组索引 使它比优先级小的索引 更小 数组在循环的时候遍历
//的速度就变快了
func (n *node) incrementChildPrio(pos int) int {cs := n.childrencs[pos].priority++prio := cs[pos].priority// Adjust position (move to front)newPos := posfor ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {// Swap node positionscs[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 emptyn.indices[pos:pos+1] + // The index char we moven.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 := pathn.priority++// Empty tree//当树是没有被初始化时if len(n.path) == 0 && len(n.indices) == 0 {n.insertChild(path, fullPath, handle)n.nType = rootreturn}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 => bn.indices = string([]byte{n.path[i]})//节点的路劲 变为 /an.path = path[:i]//由于静态节点不存在 处理器 所以 niln.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 nodeif i < len(path) {//取出节点的不包含公共前缀的部分path = path[i:]//如果是* 这样模糊匹配的节点if n.wildChild {//取出第一个孩子节点n = n.children[0]//优先级加1n.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 conflictpathSeg := pathif n.nType != catchAll {pathSeg = strings.SplitN(pathSeg, "/", 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 +"'")}}//取出第一个字符idxc := path[0]// 查找 '/' 节点下 第一个节点//如果当前节点是 param 类型节点 并且 第一个字符是 "/" 并且 只有一个子节点if n.nType == param && idxc == '/' && len(n.children) == 1 {//当前节点等于 子节点n = n.children[0]//优先级加1n.priority++//跳转到walkcontinue 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]//跳转walkcontinue 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)//刚加入的节点 优先级 +1n.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 = handlereturn}
}func (n *node) insertChild(path, fullPath string, handle Handle) {for {// 找到通配符 并返回 通配符wildcard, i, valid := findWildcard(path)//如果没有 通配符 就是跳出这个 for 循环if i < 0 { // No wilcard foundbreak}// 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 nameif 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 = :xxxif i > 0 {// Insert prefix before the current wildcardn.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/12323if len(wildcard) < len(path) {//path去除掉 已经添加的部分path = path[len(wildcard):]//下面是新建一个节点再循环 上面的步骤child := &node{priority: 1,}n.children = []*node{child}n = childcontinue}// Otherwise we're done. Insert the handle in the new leafn.handle = handlereturn} 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 pathchild := &node{wildChild: true,nType: catchAll,}n.children = []*node{child}n.indices = string('/')n = childn.priority++//存放变量 // Second node: node holding the variablechild = &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 handlen.path = pathn.handle = handle
}
Gin 框架 核心 httprouter tree树结构解析相关推荐
- Gin框架源码解析【建议收藏】
Gin框架是golang的一个常用的web框架,最近一个项目中需要使用到它,所以对这个框架进行了学习.gin包非常短小精悍,不过主要包含的路由,中间件,日志都有了.我们可以追着代码思考下,这个框架是如 ...
- 详细讲解go web框架之gin框架源码解析记录及思路流程和理解
开篇 首先gin 框架是在 官方提供的net/http标准包进行的相应封装. 那么要想理解gin框架, 就要先懂一些 net/http标准包 的相关知识. 可以参考中文的 文档: https://st ...
- Gin 框架学习笔记(01)— 自定义结构体绑定表单、绑定URI、自定义log、自定义中间件、路由组、解析查询字符串、上传文件、使用HTTP方法
要实现一个 API 服务器,首先要考虑两个方面:API 风格和媒体类型.Go 语言中常用的 API 风格是 RPC 和 REST,常用的媒体类型是 JSON.XML 和 Protobuf.在 Go A ...
- G0第25章:Gin框架进阶项目实战
1 Gin框架源码解析 通过阅读gin框架的源码来探究gin框架路由与中间件的秘密. 1.1 Gin框架路由详解 gin框架使用的是定制版本的httprouter,其路由的原理是大量使用公共前缀的树结 ...
- Go语言Gin框架源码分析
gin框架路由详解 gin框架使用的是定制版本的httprouter,其路由的原理是大量使用公共前缀的树结构,它基本上是一个紧凑的Trie tree(或者只是Radix Tree).具有公共前缀的节点 ...
- golang gin框架源码分析(二)---- 渐入佳境 摸索Engine ServeHTTP访问前缀树真正原理
文章目录 全系列总结博客链接 前引 golang gin框架源码分析(二)---- 渐入佳境 摸索Engine ServeHTTP访问前缀树真正远原理 1.再列示例代码 从示例代码入手 2.r.Run ...
- go语言学习(二)——Gin 框架简介
GoWeb开发有很多框架,如Beego.Gin.Revel.Echo.IRis等,学习框架可以快速做开发,对比常见goweb框架,通过其github的活跃度,维护的team,生产环境中的使用率以及师兄 ...
- Go 语言 Gin 框架实战项目笔记
Go 语言 Gin 框架实战 后端 Goland 操作 创建项目 运行项目 数据库操作 jwt.Response 配置管理 获取请求参数 数据验证 使用 UUID 前端 环境搭建 创建项目 ESLin ...
- 【Gin框架】框架入门
阅读目录 一.Gin 介绍 二.Gin 环境搭建 三.golang 程序的热加载 四.Gin 框架中的路由 4.1.路由概述 4.2.简单的路由配置 4.3.c.String().c.JSON().c ...
最新文章
- C++中的cin、cin.getline()、getline()函数比较
- 微信小程序开发登录界面mysql_微信小程序 欢迎界面开发的实例详解
- python多态的三种表现形式_python小结----面向对象的三大特征(封装,继承,多态)
- 实验0 了解和熟悉操作系统
- 高德地图自定义点标记大小_Vue:如何在地图上添加自定义覆盖物(点)
- mysql 辅助索引_MySQL InnoDB B+tree索引
- 朴素Paxos(Basic Paxos)算法java简易实现
- JAVA反射系列之Method,java.lang.reflect.Method的使用
- AngularJS的学习--$on、$emit和$broadcast的使用
- MATLAB仿真TSC在哪里找,-bash:tsc:找不到命令
- 58 转转技术总监骆俊武:一个核心系统 3 万多行代码的重构实战篇
- 笨办法学 Python · 续 第四部分:进阶项目
- MySQL高级-索引的使用及优化
- python numpy数组动态写入csv文件_python - 将NumPy数组转储到csv fi中
- 私有化单机题库管理软件“题库管家”1.3版本正式发布,windows版与mac版下载地址
- 贪心算法之田忌赛马问题
- 使用机器学习进行语言翻译:神经网络和seq2seq为何效果非凡?
- 隧道在线监测系统解决方案
- 微信小程序--map组件视图无法更新的问题
- Let's Use Chinaese in Flex Successfully