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树结构解析相关推荐

  1. Gin框架源码解析【建议收藏】

    Gin框架是golang的一个常用的web框架,最近一个项目中需要使用到它,所以对这个框架进行了学习.gin包非常短小精悍,不过主要包含的路由,中间件,日志都有了.我们可以追着代码思考下,这个框架是如 ...

  2. 详细讲解go web框架之gin框架源码解析记录及思路流程和理解

    开篇 首先gin 框架是在 官方提供的net/http标准包进行的相应封装. 那么要想理解gin框架, 就要先懂一些 net/http标准包 的相关知识. 可以参考中文的 文档: https://st ...

  3. Gin 框架学习笔记(01)— 自定义结构体绑定表单、绑定URI、自定义log、自定义中间件、路由组、解析查询字符串、上传文件、使用HTTP方法

    要实现一个 API 服务器,首先要考虑两个方面:API 风格和媒体类型.Go 语言中常用的 API 风格是 RPC 和 REST,常用的媒体类型是 JSON.XML 和 Protobuf.在 Go A ...

  4. G0第25章:Gin框架进阶项目实战

    1 Gin框架源码解析 通过阅读gin框架的源码来探究gin框架路由与中间件的秘密. 1.1 Gin框架路由详解 gin框架使用的是定制版本的httprouter,其路由的原理是大量使用公共前缀的树结 ...

  5. Go语言Gin框架源码分析

    gin框架路由详解 gin框架使用的是定制版本的httprouter,其路由的原理是大量使用公共前缀的树结构,它基本上是一个紧凑的Trie tree(或者只是Radix Tree).具有公共前缀的节点 ...

  6. golang gin框架源码分析(二)---- 渐入佳境 摸索Engine ServeHTTP访问前缀树真正原理

    文章目录 全系列总结博客链接 前引 golang gin框架源码分析(二)---- 渐入佳境 摸索Engine ServeHTTP访问前缀树真正远原理 1.再列示例代码 从示例代码入手 2.r.Run ...

  7. go语言学习(二)——Gin 框架简介

    GoWeb开发有很多框架,如Beego.Gin.Revel.Echo.IRis等,学习框架可以快速做开发,对比常见goweb框架,通过其github的活跃度,维护的team,生产环境中的使用率以及师兄 ...

  8. Go 语言 Gin 框架实战项目笔记

    Go 语言 Gin 框架实战 后端 Goland 操作 创建项目 运行项目 数据库操作 jwt.Response 配置管理 获取请求参数 数据验证 使用 UUID 前端 环境搭建 创建项目 ESLin ...

  9. 【Gin框架】框架入门

    阅读目录 一.Gin 介绍 二.Gin 环境搭建 三.golang 程序的热加载 四.Gin 框架中的路由 4.1.路由概述 4.2.简单的路由配置 4.3.c.String().c.JSON().c ...

最新文章

  1. C++中的cin、cin.getline()、getline()函数比较
  2. 微信小程序开发登录界面mysql_微信小程序 欢迎界面开发的实例详解
  3. python多态的三种表现形式_python小结----面向对象的三大特征(封装,继承,多态)
  4. 实验0 了解和熟悉操作系统
  5. 高德地图自定义点标记大小_Vue:如何在地图上添加自定义覆盖物(点)
  6. mysql 辅助索引_MySQL InnoDB B+tree索引
  7. 朴素Paxos(Basic Paxos)算法java简易实现
  8. JAVA反射系列之Method,java.lang.reflect.Method的使用
  9. AngularJS的学习--$on、$emit和$broadcast的使用
  10. MATLAB仿真TSC在哪里找,-bash:tsc:找不到命令
  11. 58 转转技术总监骆俊武:一个核心系统 3 万多行代码的重构实战篇
  12. 笨办法学 Python · 续 第四部分:进阶项目
  13. MySQL高级-索引的使用及优化
  14. python numpy数组动态写入csv文件_python - 将NumPy数组转储到csv fi中
  15. 私有化单机题库管理软件“题库管家”1.3版本正式发布,windows版与mac版下载地址
  16. 贪心算法之田忌赛马问题
  17. 使用机器学习进行语言翻译:神经网络和seq2seq为何效果非凡?
  18. 隧道在线监测系统解决方案
  19. 微信小程序--map组件视图无法更新的问题
  20. Let's Use Chinaese in Flex Successfully

热门文章

  1. 台式计算机电池,带电池的台式机 华硕M51家用电脑首测
  2. dell R720服务器现场上门数据恢复
  3. 微信小程序 + vant组件van-card 图片解析不到
  4. 20194311姜晨昊Exp-8 Web综合
  5. 快毕业的时候,研究生就业率还不到9%!全省最强的211大学就业率这么低?
  6. 离线截图编辑软件-Snipaste
  7. AGV叉车的调度系统简介
  8. JavaScript动态钟表
  9. linux播放器安卓系统下载软件,linux系统如何安装在线播放器和QQ?等常用软件?
  10. 兄弟8400cdn耗材_彩色激光一体机