golang gin框架源码分析(一)---- 下载gin框架源码 运行示例代码 由浅入深探寻原理


全系列总结博客链接


golang Gin框架源码分析 全系列总结博客


前引


1月29日 复工了 但是公司没什么人来 毕竟今天是开工第一天 我也就很晚很晚的起床 骑自行车来到了公司

在公司上班的这些时间 感觉焦虑和思考还是没有很大程度的减少 因为感觉在异地工作的感觉还是没有太多的归属感 最好最好的归处也就是回到我的hometown 成都 所以这段时间也打算再骑驴找马 边工作边再准备准备面试 再沉淀一下 因为我发现工作的时候 也是要给自己找事情做 其实实习留给自己空余的时间还是相当多的 那这个就需要还是有一个很明确的目标 在空余时间干什么了

至少在刚开始的一个月 我已经远离了我的本 也就是刷题和刷面经 已经很久很久没有再看过了 而这段时间我开始捡起来了 如果现在在的是成都腾讯 我觉得可能我的状态和想做的事情 应该是要比远走他乡一个人在陌生的城市 应该状态还要更好一些

所以我接下来的打算还是 3月的时候再投投成都的字节跳动 骑驴找马一下… 毕竟现在也还是实习 而且有腾讯的实习背板 简历关和印象分应该还是要比很多现在没有实习经历的要好很多的 可能不会出现简历秒挂的情况了

现在尽管是找到了实习 但感觉仍然不能有一刻的停歇 因为目前的大环境还是没有那么理想 而且我现在距离秋招 面经也是不能有丝毫的懈怠 平时上班干活的时候 其实还是很累的 下班回去其实也没有什么多余的时间留给我去休息了 那刷面经 准备秋招的时间其实这段时间只能像是海绵里的水 再挤出来了

如果3月份找到了字节的暑期实习 可能心情就要稍微缓一缓了 没有找到只能暑假继续在腾讯干了 或者辞职全力准备秋招

路还很长 真的很长啊

现在写博客出来 其实是当作激励我自己 因为写出来了 我必须真的懂 真的会 我能够写出来
那不会只能逼自己看懂 再写出来
那就这样吧 毕竟项目中也用了gin框架 面试中问到了也有的讲

**这里最后套个盾 **
go初学者 有些地方没理解或者理解错误 烦请谅解 = =


golang gin框架源码分析(一)---- 下载gin框架源码 运行示例代码 由浅入深探寻原理


1、简单写个示例代码


下面简单写个示例代码 写个hello world响应吧
但其实这种写法 第一种HandleFunc它是没有意义的 因为所有的都会调用ServeHTTP 那等于这个也没用了
这个其实是go讲web相关掉用函数的已经包装好了 所以用go语言相比其他语言编写web相关的程序是非常简单的 标准库已经提前包装好了这些函数 两行函数就可以简单实现一个easy web

我们也可以换成gin框架来写一个hello, world响应
发现也是很简单 那我们不妨就从下方深入吧

package main

import "net/http"

type helloHandler struct{}

func (handler *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("hello, world")) // 向响应中写入访问的路径
}

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`hello world`))
	})
	http.ListenAndServe(":8002", &helloHandler{})
}

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
)

type helloHandler struct{}

func (handler *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("hello, world")) // 向响应中写入访问的路径
}

func helloController(c *gin.Context) {
	c.String(http.StatusOK, "hello, world")
}

func main() {
	/*http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
	  	w.Write([]byte(`hello world`))
	  })
	  http.ListenAndServe(":8002", &helloHandler{})*/

	r := gin.Default()
	r.GET("/", helloController)
	r.Run(fmt.Sprintf(":%d", 8002))
}


响应效果如下
在这里插入图片描述


2、从示例代码由浅入深研究


下面的例子大概包括了基本上一些 用gin框架里面的一些常见的应用场景 也就是一般用gin框架用得最多的几个地方 那我们就从下面的几个函数入手即可

这里我就不介绍每个地方是大概怎么样的效果了 主要是感觉介绍这个就有点保姆级了 有点子浪费时间

package main

import (
	"fmt"
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"
)

type helloHandler struct{}

func (handler *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("hello, world")) // 向响应中写入访问的路径
}

func helloController(c *gin.Context) {
	c.String(http.StatusOK, "hello, world")
}

func middlewareTest() gin.HandlerFunc {
	return func(c *gin.Context) {
		_, ok := c.GetQuery("key111")
		if !ok {
			c.Abort()
			c.String(http.StatusOK, "Cant find param key111")
			return
		}
		ts := c.Request.Header.Get("timestamp")
		its, err := strconv.Atoi(ts)
		if err != nil || its == 0 {
			c.Abort()
			if err != nil {
				c.String(http.StatusOK, err.Error())
			} else {
				c.String(http.StatusOK, "timestamp is zero")
			}
			return
		}
		c.Next()
	}
}

func get111Controller(c *gin.Context) {
	c.String(http.StatusOK, "get111Controller")
}

func get222Controller(c *gin.Context) {
	c.String(http.StatusOK, "get222Controller")
}

func main() {
	/*http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
	  	w.Write([]byte(`hello world`))
	  })
	  http.ListenAndServe(":8002", &helloHandler{})*/

	r := gin.Default()
	r.GET("/gettest", helloController)

	tmpgroup := r.Group("/test")
	tmpgroup.Use(middlewareTest())
	{
		tmpgroup.GET("/get111", get111Controller)
		tmpgroup.GET("/get222", get222Controller)
	}

	r.Run(fmt.Sprintf(":%d", 8002))
}


1、gin.Default() 默认Engine

gin的默认引擎 Engine
也就是我们的路由引擎 我们可以把它看作一辆汽车 装载了非常非常多的东西
下面是 生成默认引擎的函数

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine
}

type Engine struct {
	RouterGroup
	RedirectTrailingSlash bool
	RedirectFixedPath bool
	HandleMethodNotAllowed bool
	ForwardedByClientIP bool
	AppEngine bool
	UseRawPath bool
	UnescapePathValues bool
	RemoveExtraSlash bool
	RemoteIPHeaders []string
	TrustedPlatform string
	MaxMultipartMemory int64
	UseH2C bool
	ContextWithFallback bool

	delims           render.Delims
	secureJSONPrefix string
	HTMLRender       render.HTMLRender
	FuncMap          template.FuncMap
	allNoRoute       HandlersChain
	allNoMethod      HandlersChain
	noRoute          HandlersChain
	noMethod         HandlersChain
	pool             sync.Pool
	trees            methodTrees
	maxParams        uint16
	maxSections      uint16
	trustedProxies   []string
	trustedCIDRs     []*net.IPNet
}

其中 我们看到Engine 我认为最核心的就是其中的 RouterGroup 当然这个我们可以后面再看看 等用到的时候再来看

然后我们获取了这样一个引擎的结构体 我们想要让对应的url有对应的处理函数 对应的处理模式 是POST 还是 GET 还是PUT/DELETE 还是其他的 就是下面的语句

r.GET("/gettest", helloController)

2、r.GET(url, handler …HandlerFunc) IRoutes

我们每次为对应的url 都有相对应的处理函数 那么例如下面
唯一的区别就是 HTTP的请求方式不同 那么相同url 如果请求方法不同 也是没有办法找到其对应的处理函数

r.GET("/gettest", helloController)
r.POST("/gettest", helloController)

那我们找一下其对应的底层实现 实现就简单的一句话 group.handler 主要原因是 engine内包含了RouterGroup 所以方法也是可以直接用内部的RouterGroup的

而我上面也说了 最重要的engine 中的发动机 也就是我们函数路径和实现函数 全都存储在这个RouterGroup中 这里简单的将请求方式GET 转化为请求方式和 handlers 因为可以有多个函数去处理一个url 基本上第一个通常我们需要去鉴权 是否可以经过此 然后检查检查头部的参数

并且的话 下面的relativepath 我们后面也可以看见 不仅是可以直接在直接的engine上设置绝对路径 我们也可以路径分组 将相同前缀的单独设置一个group 然后不同的相对路径单独设置函数 也就是这个语句后面的group

而绝对路径和 相同前缀 都存储在RouterGroup中 定义也贴在了下面 我们由basepath 去加上 relativePath也就可以得到absolutePath绝对路径 其实后面也能看到我们的去寻址 可以发现有点像是字典树一样的 我们去进行找到对应url对应的函数 这是后话了

// RouterGroup is used internally to configure router, a RouterGroup is associated with
// a prefix and an array of handlers (middleware).
type RouterGroup struct {
	Handlers HandlersChain
	basePath string
	engine   *Engine
	root     bool
}

// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodGet, relativePath, handlers)
}

1、group.handle(httpMethod, relativePath, handlers)

递归的来到这个函数定义
最上方的就是我说的需要去找到我们的绝对路径 由如果设置了group且设置了basepath 则路径就不像是默认引擎一样 bathpath为`/

得到最终路径后 这里我们就要得到我们的最终的Handlers数组了
我们所有的处理函数我们都是以数组存储了起来 就如下方我贴了出来 最后也就用我们的copy函数简单的copy了进去

得到了最后的handlers 我上面也说过了 每个小组 也是通过结构体内engine 找回原来的引擎 因为最后运行的时候 还是以engine的函数 ServerHTTP为最后的处理函数 engine是最后的处理体 我们要将对应的urlabsolutepath添加入engine 这个也是非常非常核心的一个地方 我们慢慢来

func joinPaths(absolutePath, relativePath string) string {
	if relativePath == "" {
		return absolutePath
	}

	finalPath := path.Join(absolutePath, relativePath)
	if lastChar(relativePath) == '/' && lastChar(finalPath) != '/' {
		return finalPath + "/"
	}
	return finalPath
}

func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
	return joinPaths(group.basePath, relativePath)
}
-----------------------------
// HandlersChain defines a HandlerFunc slice.
type HandlersChain []HandlerFunc

// RouterGroup is used internally to configure router, a RouterGroup is associated with
// a prefix and an array of handlers (middleware).
type RouterGroup struct {
	Handlers HandlersChain
	basePath string
	engine   *Engine
	root     bool
}

func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
	finalSize := len(group.Handlers) + len(handlers)
	assert1(finalSize < int(abortIndex), "too many handlers")
	mergedHandlers := make(HandlersChain, finalSize)
	copy(mergedHandlers, group.Handlers)
	copy(mergedHandlers[len(group.Handlers):], handlers)
	return mergedHandlers
--------------------------------

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
	absolutePath := group.calculateAbsolutePath(relativePath)
	handlers = group.combineHandlers(handlers)
	group.engine.addRoute(httpMethod, absolutePath, handlers)
	return group.returnObj()
}

2、group.engine.addRoute(httpMethod, absolutePath, handlers)(上)

前面三个 assert断言 绝对路径 请求方式不能为空 至少存在一个handlerfunc

root := engine.trees.get(method) 有点核心了 这个trees的定义我贴在了下方
这下子定义就很清楚了 trees是以method不同的根结点 举个例也就是 最多也就有几个根结点 且根结点是以method取值 遍历根节点数组 找到其定义的method(例get/post) 没找到则新建立一个当前method的根结点

例如我们当前的例子 trees里面肯定是没有我们的method对应的node的 则新建 新建的代码也就是if root == nil这里面 很容易就能理解


接下来就是这里面的最重要的一个了 root.addRoute(path, handlers)
我们到更下方详细看看这个函数

root := engine.trees.get(method)

type Engine struct {
	RouterGroup
	...
	...
	FuncMap          template.FuncMap
	allNoRoute       HandlersChain
	allNoMethod      HandlersChain
	noRoute          HandlersChain
	noMethod         HandlersChain
	pool             sync.Pool
	trees            methodTrees
}
type methodTrees []methodTree
type methodTree struct {
	method string
	root   *node
}

func (trees methodTrees) get(method string) *node {
	for _, tree := range trees {
		if tree.method == method {
			return tree.root
		}
	}
	return nil
}
------------



-----

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
	assert1(path[0] == '/', "path must begin with '/'")
	assert1(method != "", "HTTP method can not be empty")
	assert1(len(handlers) > 0, "there must be at least one handler")

	debugPrintRoute(method, path, handlers)

	root := engine.trees.get(method)
	if root == nil {
		root = new(node)
		root.fullPath = "/"
		engine.trees = append(engine.trees, methodTree{method: method, root: root})
	}
	root.addRoute(path, handlers)

	// Update maxParams
	if paramsCount := countParams(path); paramsCount > engine.maxParams {
		engine.maxParams = paramsCount
	}

	if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
		engine.maxSections = sectionsCount
	}
}

3、root.addRoute(path, handlers)(中)

root 添加路由 绝对路径 + handlers

一点点慢慢分析吧 对于刚开始新的node 那么刚开始tree确实就是空的 那么我们就进入n.insertChild(path, fullPath, handlers)

对于insertChild处理通配符的地方 我就没有贴出来了 如果只是设置的路径没有额外的通配符 就只有3行 简单的将值赋值

那我们先顺着这个逻辑 重新回去group.engine.addRoute

root.addRoute(path, handlers)

type node struct {
	path      string
	indices   string
	wildChild bool
	nType     nodeType
	priority  uint32
	children  []*node // child nodes, at most 1 :param style node at the end of the array
	handlers  HandlersChain
	fullPath  string
}
--------
func (n *node) addRoute(path string, handlers HandlersChain) {
	fullPath := path
	n.priority++

	// Empty tree
	if len(n.path) == 0 && len(n.children) == 0 {
		n.insertChild(path, fullPath, handlers)
		n.nType = root
		return
	}

	.............
}
------
func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) {
	// 处理wildcard *通配符匹配
	....
	....

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

4、group.engine.addRoute(httpMethod, absolutePath, handlers)(下)

这里其实看起来很多 所做的工作其实也就只有几点
合并字典树 切分字典树 有需求将节点切分合并就合并 切分就切分 节点分裂新起父节点等等
写过字典树的应该也就明白了

最详细的内部操作我就没看了 = = 也就是注册url前缀树 并且还会按照匹配度去调换位置 内部也有节点的权重比 权重越高的则会优先排在前面 主要干的也就这个工作了

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
		if i < len(n.path) {
			child := node{
				path:      n.path[i:],
				wildChild: n.wildChild,
				nType:     static,
				indices:   n.indices,
				children:  n.children,
				handlers:  n.handlers,
				priority:  n.priority - 1,
				fullPath:  n.fullPath,
			}

			n.children = []*node{&child}
			// []byte for proper unicode char conversion, see #65
			n.indices = bytesconv.BytesToString([]byte{n.path[i]})
			n.path = path[:i]
			n.handlers = nil
			n.wildChild = false
			n.fullPath = fullPath[:parentFullPathIndex+i]
		}

		// Make new node a child of this node
		if i < len(path) {
			path = path[i:]
			c := path[0]

			// '/' after param
			if n.nType == param && c == '/' && len(n.children) == 1 {
				parentFullPathIndex += len(n.path)
				n = n.children[0]
				n.priority++
				continue walk
			}

			// Check if a child with the next path byte exists
			for i, max := 0, len(n.indices); i < max; i++ {
				if c == n.indices[i] {
					parentFullPathIndex += len(n.path)
					i = n.incrementChildPrio(i)
					n = n.children[i]
					continue walk
				}
			}

			// Otherwise insert it
			if c != ':' && c != '*' && n.nType != catchAll {
				// []byte for proper unicode char conversion, see #65
				n.indices += bytesconv.BytesToString([]byte{c})
				child := &node{
					fullPath: fullPath,
				}
				n.addChild(child)
				n.incrementChildPrio(len(n.indices) - 1)
				n = child
			} else if n.wildChild {
				// inserting a wildcard node, need to check if it conflicts with the existing wildcard
				n = n.children[len(n.children)-1]
				n.priority++

				// Check if the wildcard matches
				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
				}

				// 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 +
					"'")
			}

			n.insertChild(path, fullPath, handlers)
			return
		}

		// Otherwise add handle to current node
		if n.handlers != nil {
			panic("handlers are already registered for path '" + fullPath + "'")
		}
		n.handlers = handlers
		n.fullPath = fullPath
		return
	}
}

3、简单总结一下


上面主要简单看了看 gin框架的大致结构 还是比较详细的 详略得当的把gin get 我们所做的工作给写了进去 对于通配符 以及前缀树中树的切分以及变换就没详细分析了

但是经过上面的分析 其实还是能够把很多很重要的地方分析出来了
感觉距离gin框架的大致把握的路在不久的将来了 下一篇可以详细看看 engineServeHTTP中的如何使用Context这个大杀器 缓存请求消息和相应消息 以及 gin对对象分配是如何缓存的

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Love 6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值