【golang自学之路(二)】源码分析gin框架路由注册,存储,寻找


在这里插入图片描述

前言

学习完golang基础语法后,学习gin框架,参考李文周老师的博客进行学习。
gin框架主要分为一下二个部分
1.gin框架的路由注册
2.gin框架的中间件使用

最常见的gin框架使用如霞:

package main

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


func main()  {

	r := gin.Default()

	r.GET("/",func(context *gin.Context){
		context.JSON(200,gin.H{
			"massage":"hello",
		})
	})


	r.Run()
}

gin.Default(),r.GET() , r.Run()都干了什么接下来通过源码详细探索一下。


Run方法

r.Run()

// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
// It is a shortcut for http.ListenAndServe(addr, router)
// Note: this method will block the calling goroutine indefinitely unless an error happens.
func (engine *Engine) Run(addr ...string) (err error) {
	defer func() { debugPrintError(err) }()

	if engine.isUnsafeTrustedProxies() {
		debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
			"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
	}

	address := resolveAddress(addr)
	debugPrint("Listening and serving HTTP on %s\n", address)
	err = http.ListenAndServe(address, engine.Handler())
	return
}

可以看到Run方法调用的是Engine结构体指针,这个结构体指针是gin.Default()获取的,并且真正执行http方法的是err = http.ListenAndServe(address, engine.Handler()),传入了engine。所以我们来看一下Engine这个结构体。

r.Run()

type Engine struct {
	RouterGroup
	RemoveExtraSlash bool
	RemoteIPHeaders []string
	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
}

var _ IRouter = &Engine{}

Engine 继承了RouterGroup结构体,并且实现了Handler接口。在var _ IRouter = &Engine{},这句的意思这种方法的作用就是确保一个结构体实现了这个方法,把问题暴露在编译阶段如果这个ENgine引擎没有实现Irouter结构就报错,属于_妙用。

在这里插入图片描述
其中Engine结构体中有methodTrees,type methodTrees []methodTree而methodTree这个结构体,有两个属性一个string,一个node指针。而node指针是典型的PAT树节点。

在这里插入图片描述
PAT是一种更节省空间的前缀树。相较于普通前缀树,他会把公共部分单独形成一个子节点。而gin框架在有新的路由注册时候,会将原来注册的和新注册的提取公共部分,作为新的父亲节点。在gin框架中,每一类请求 get post 请求都是一颗上面这样的pat前缀树。当一个用户请求一个url的时候,gin框架去做路由匹配的原理是采用路径补偿,直接去当前节中匹配最长的子节点,最后匹配最短的。

在这里插入图片描述

在这里插入图片描述

而Handler结构有一个HTTP方法,我们看Engine是如何实现的
在这里插入图片描述

ServeHTTP

// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	c := engine.pool.Get().(*Context)
	c.writermem.reset(w)
	c.Request = req
	c.reset()

	engine.handleHTTPRequest(c)

	engine.pool.Put(c)
}

engine.pool.Get().(*Context)典型对象池,减少gc,减少内存申请Get就是从池中获取一个对象,并强制转换为一个context指针。
c.reset()在取出池子之后做初始化逻辑和java线程池有点区别java线程池要在放入池子的时侯gc这个是放直接放,取的时候初始化。
engine.handleHTTPRequest(c)处理http请求
engine.pool.Put(c)放回池子中

handleHTTPRequest

func (engine *Engine) handleHTTPRequest(c *Context) {
	httpMethod := c.Request.Method
	rPath := c.Request.URL.Path
	unescape := false
	if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
		rPath = c.Request.URL.RawPath
		unescape = engine.UnescapePathValues
	}

	if engine.RemoveExtraSlash {
		rPath = cleanPath(rPath)
	}

	// Find root of the tree for the given HTTP method
	t := engine.trees
	for i, tl := 0, len(t); i < tl; i++ {
		if t[i].method != httpMethod {
			continue
		}
		root := t[i].root
		// Find route in tree
		value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
		if value.params != nil {
			c.Params = *value.params
		}
		if value.handlers != nil {
			c.handlers = value.handlers
			c.fullPath = value.fullPath
			c.Next()
			c.writermem.WriteHeaderNow()
			return
		}
		if httpMethod != http.MethodConnect && rPath != "/" {
			if value.tsr && engine.RedirectTrailingSlash {
				redirectTrailingSlash(c)
				return
			}
			if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
				return
			}
		}
		break
	}

	if engine.HandleMethodNotAllowed {
		for _, tree := range engine.trees {
			if tree.method == httpMethod {
				continue
			}
			if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
				c.handlers = engine.allNoMethod
				serveError(c, http.StatusMethodNotAllowed, default405Body)
				return
			}
		}
	}
	c.handlers = engine.allNoRoute
	serveError(c, http.StatusNotFound, default404Body)
}

for i, tl := 0, len(t); i < tl; i++ {相比于for i, tl := 0, i < len(t); i++ {方法萧少了每次求len的方法调用值得学习。
if t[i].method != httpMethod { continue } 可以看到这是在匹配httpMEthod方法,我们常知道的http方法,如get,post,put,delete方法。在这engine会去取自己的属性tree,通过node接单中的方法确认接下来是那种方法调用。

method 采用切片方式去设计node节点,因为httpMethod一共九种,直接遍历即可。

New()

func New() *Engine {
	debugPrintWARNINGNew()
	engine := &Engine{
		RouterGroup: RouterGroup{
			Handlers: nil,
			basePath: "/",
			root:     true,
		},
		FuncMap:                template.FuncMap{},
		RedirectTrailingSlash:  true,
		RedirectFixedPath:      false,
		HandleMethodNotAllowed: false,
		ForwardedByClientIP:    true,
		RemoteIPHeaders:        []string{"X-Forwarded-For", "X-Real-IP"},
		TrustedPlatform:        defaultPlatform,
		UseRawPath:             false,
		RemoveExtraSlash:       false,
		UnescapePathValues:     true,
		MaxMultipartMemory:     defaultMultipartMemory,
		trees:                  make(methodTrees, 0, 9),
		delims:                 render.Delims{Left: "{{", Right: "}}"},
		secureJSONPrefix:       "while(1);",
		trustedProxies:         []string{"0.0.0.0/0", "::/0"},
		trustedCIDRs:           defaultTrustedCIDRs,
	}

make(methodTrees, 0, 9)可以看到new方法中直接创建了一个容量为9的固定切片


注册路由

注册路由方法

r.GET("/",func(context *gin.Context){
		context.JSON(200,gin.H{
			"massage":"hello",
		})
	})

可以看到gin框架中注册路由直接采用的api,我们来看一下get方法

GET

func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodGet, relativePath, handlers)
}


因为engine继承了RouterGroup,而type HandlersChain []HandlerFunc采用切片去保存的,而HandlersChain 就是func函数的集合type HandlerFunc func(*Context)。而方法入参可以看到是可以传递多个func的。而核心方法是RouterGroup中的hander方法。
在这里插入图片描述

handle

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()
}

group.calculateAbsolutePath(relativePath)是将传入进来的路由地址转为HandlersChain类型

group.combineHandlers(handlers)方法如下,就是将原来方法中的路由和新注册的路由存放到新的一个slice中。首先先获取最大长度,之后申请一个最大长度的slice,然后将group中的先放入slice,然后再将handlers放入(位置在group之后)。

combineHandlers

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
}

group.engine.addRoute(httpMethod, absolutePath, handlers)是添加路由方法,核心方法在root.addRoute(path, handlers)这个方法就是向PAT树去注册新节点。

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
	}
}

路由寻找

当我们启动服务的时候,请求一个get请求在gin框架中是怎么为我们寻找到我们请求的接口的?

engine.handleHTTPRequest©

func (engine *Engine) handleHTTPRequest(c *Context) {
	httpMethod := c.Request.Method
	rPath := c.Request.URL.Path
	unescape := false
	if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
		rPath = c.Request.URL.RawPath
		unescape = engine.UnescapePathValues
	}

	if engine.RemoveExtraSlash {
		rPath = cleanPath(rPath)
	}

	// Find root of the tree for the given HTTP method
	t := engine.trees
	for i, tl := 0, len(t); i < tl; i++ {
		if t[i].method != httpMethod {
			continue
		}
		root := t[i].root
		// Find route in tree
		value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
		if value.params != nil {
			c.Params = *value.params
		}
		if value.handlers != nil {
			c.handlers = value.handlers
			c.fullPath = value.fullPath
			c.Next()
			c.writermem.WriteHeaderNow()
			return
		}
		if httpMethod != http.MethodConnect && rPath != "/" {
			if value.tsr && engine.RedirectTrailingSlash {
				redirectTrailingSlash(c)
				return
			}
			if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
				return
			}
		}
		break
	}

	if engine.HandleMethodNotAllowed {
		for _, tree := range engine.trees {
			if tree.method == httpMethod {
				continue
			}
			if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
				c.handlers = engine.allNoMethod
				serveError(c, http.StatusMethodNotAllowed, default405Body)
				return
			}
		}
	}
	c.handlers = engine.allNoRoute
	serveError(c, http.StatusNotFound, default404Body)
}

我们来看handleHTTPRequest方法中,value := root.getValue(rPath, c.params, c.skippedNodes, unescape),而这个方法中,返回nodeValue保存注册的处理函数和匹配到的路径参数数据。
在这里插入图片描述

err = http.ListenAndServe(address, engine.Handler())

func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

默认中间件

gin.Default()默认使用了Logger和Recovery中间件(Gin中的中间件必须是一个gin.HandlerFunc类型。),其中:
Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release。
Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。

Default

// 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
}

// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}

   

可以看到Default方法中Use注册的两个中间件。使用了RouterGroup中的Use方法这个方法就是往HandlerFunc中去append新注册的中间件,而group.Handlers本身是HandlerFunc的slice。
而handleHTTPRequest中用到了engine的Next()方法,此方法的作用就是继续执行
在这里插入图片描述

接下来对next方法进行测试

main

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

func func1(c *gin.Context)  {
	fmt.Println("into func1")
	c.Next()
	fmt.Println("out func1")
}

func func2(c *gin.Context)  {
	fmt.Println("into func2")
	c.Next()
	fmt.Println("out func2")
}

func func3(c *gin.Context)  {
	fmt.Println("into func3")
	c.Next()
	fmt.Println("out func3")
}

func main()  {

	r := gin.Default()

	r.GET("/hello",func1,func2,func3)


	r.Run()
}

   

在这里插入图片描述

在这里插入图片描述而NExt方法的源码

Next()

func (c *Context) Next() {
	c.index++
	for c.index < int8(len(c.handlers)) {
		c.handlers[c.index](c)
		c.index++
	}
}

   

总结

在观看文档的同时自己点击源码并学习。

我的目标

希望在年底学习一下内容:
java学习内容:
1.tomcat源码
2.dubbo源码
3.zookeeper源码
4.netty源码
go学习内容:
1.gin框架学习
2.简单go项目
3.go基础知识进阶(gmp,gc,channel,map,slice源码等)
中间件学习内容:
1.kafka使用及源码
框架学习内容:
1.从零开始学架构
算法学习内容:
1.复习leetcode中top 100

  • 35
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值