全网最详细的gin源码解析

介绍

  • gin 框架基于 httprouter 实现最重要的路由模块,采用类似字典树一样的数据结构来存储路由与handle方法的映射.也是框架高性能的原因,有兴趣的同学可以自行查阅
  • 本文提供 在线思维导图 搭配文章看事半功倍
  • Engine 容器对象,整个框架的基础
  • Engine.trees 负责存储路由和handle方法的映射,采用类似字典树的结构
  • Engine.RouterGroup,其中的Handlers存储着所有中间件
  • Context上下文对象,负责处理请求和回应,其中的handlers是存储处理请求时中间件和处理方法的

初始化容器

通过调用 gin.New() 方法来实例化Engine.
虽然参数很多,但我们只需要注意 RouterGroup ,treesengine.pool.New即可

  • engine.pool.New 负责创建Context对象,采用sync.Pool减少频繁context实例化带来的资源消耗,
func New() *Engine {
	debugPrintWARNINGNew()
	engine := &Engine{
		//实例化RouterGroup,其中Handlers为中间件数组
		RouterGroup: RouterGroup{ 
			Handlers: nil,
			basePath: "/",
			root:     true,
		},
		FuncMap:                template.FuncMap{},
		RedirectTrailingSlash:  true,
		RedirectFixedPath:      false,
		HandleMethodNotAllowed: false,
		ForwardedByClientIP:    true,
		AppEngine:              defaultAppEngine,
		UseRawPath:             false,
		RemoveExtraSlash:       false,
		UnescapePathValues:     true,
		MaxMultipartMemory:     defaultMultipartMemory,
		//trees 是最重要的点!!!!负责存储路由和handle方法的映射,采用类似字典树的结构
		trees:                  make(methodTrees, 0, 9), 
		delims:                 render.Delims{Left: "{{", Right: "}}"},
		secureJsonPrefix:       "while(1);",
	}
	engine.RouterGroup.engine = engine
	//这里采用 sync/pool 实现context池,减少频繁context实例化带来的资源消耗
	engine.pool.New = func() interface{} {
		return engine.allocateContext()
	}
	return engine
}

注册中间件

gin的高性能主要依靠trees,每一个节点的内容你可以想象成一个key->value的字典树,key是路由,而value则是一个[]HandlerFunc,里面存储的就是按顺序执行的中间件和handle控制器方法,这里很重要,要考!

注册全局中间件

gin.use() 调用RouterGroup.Use()RouterGroup.Handlers写入记录

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
	engine.RouterGroup.Use(middleware...) 
	engine.rebuild404Handlers() //注册404处理方法
	engine.rebuild405Handlers() //注册405处理方法
	return engine
}

//  其中`Handlers`字段就是一个数组,用来存储中间件
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}

注册路由组中间件

  • 通过 Group()方法返回一个 新生成的RouterGroup指针,用来分开每个路由组加载不一样的中间件
  • 注意这里的Handlers: group.combineHandlers(handlers),这行代码会复制一份全局中间件到新生成的RouterGroup.Handlers中,接下来路由注册的时候就可以一起写入树节点中
group := g.Group("/test_group")
group.Use(middleware.Test())
{
	//这里会最终路由和中间件以及handle方法一起写入树节点中
	group.GET("/test",handler.TestTool)
}

//返回一个RouterGroup指针
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
	return &RouterGroup{
		//复制一份全局中间件
		Handlers: group.combineHandlers(handlers),
		basePath: group.calculateAbsolutePath(relativePath),
		engine:   group.engine,
	}
}

注册路由以及中间件

不管哪种请求方式最终都会调用RouterGroup.handle,这个方法主要有两个作用

  • 处理路由的格式,将路由拼成 ‘/’ 字符开头的路由

  • 复制一份RouterGroup.Handlers,加上相应这次路由的handle方法,组成一个list放入树节点中

  • 最后调用trees.addRoute增加节点

g.GET("/test_tool", middleware.Test(),handler.TestTool)

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
 	//根目录和路由结合起来,将路由拼成 '/' 字符开头的路由
	absolutePath := group.calculateAbsolutePath(relativePath) 
	//复制一份RouterGroup.Handlers,加上相应这次路由的handle方法,组成一个list放入树节点中
	handlers = group.combineHandlers(handlers) 
	group.engine.addRoute(httpMethod, absolutePath, handlers)
	return group.returnObj()
}

//调用 `trees`增加节点
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)
}

启动

通过调用net/http来启动服务,由于engine实现了ServeHTTP方法,只需要直接传engine对象就可以完成初始化并启动

g.Run()

func (engine *Engine) Run(addr ...string) (err error) {
	defer func() { debugPrintError(err) }()

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

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

//来自 net/http 定义的接口,只要实现了这个接口就可以作为处理请求的函数
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

//实现了ServeHTTP方法
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)
}

处理请求

  • 这里只需要留意handleHTTPRequest(c *Context)方法就好了
  • 通过请求方法和路由找到相对应的树节点,获取储存的[]HandlerFunc列表,通过调用c.Next()处理请求
  • 通过不停的移动下标递归,最后完成处理返回结果
func (engine *Engine) handleHTTPRequest(c *Context) {
	...
	// 
	t := engine.trees
	for i, tl := 0, len(t); i < tl; i++ {
		...
		// Find route in tree
		value := root.getValue(rPath, c.Params, unescape)
		if value.handlers != nil {
			c.handlers = value.handlers
			c.Params = value.params
			c.fullPath = value.fullPath
			c.Next()
			c.writermem.WriteHeaderNow()
			return
		}
		...
	}
	...
}

//这里挺巧妙的,通过不停的移动下标递归,最后完成处理返回结果
func (c *Context) Next() {
	c.index++
	for c.index < int8(len(c.handlers)) {
		c.handlers[c.index](c)
		c.index++
	}
}

感想

  • gin框架源码算是比较简单易懂的,这恰恰就是他的优点,golang语言本身就比较成熟,框架只不过一个方便你做项目脚手架,你完全可以按照你的需求来定制你自己专属的gin框架,包括日志,缓存,队列等等
  • 核心是路由存储树,学好算法,数据结构才是关键

参考文档

gin中文文档 https://gin-gonic.com/zh-cn/docs/introduction/
gin项目地址 https://github.com/gin-gonic/gin
httprouter https://github.com/julienschmidt/httprouter

  • 9
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
对于全网详细的VSCode教程,以下是一个简短的描述: 全网详细的VSCode教程应该包含以下内容:基本介绍、安装和设置、编辑器布局、常用快捷键、实用插件和扩展、调试功能、版本控制、代码片段等。 在基本介绍部分,应该详细介绍VSCode是什么,它的优点和特点,如何下载和安装等。 安装和设置部分应该涵盖不同操作系统上的安装步骤和注意事项。同时,还应该介绍不同配置选项,如主题、字体、缩进设置等。 编辑器布局部分应该解释各个面板和视图的作用,如侧边栏、编辑窗口、终端等。详细说明如何调整布局以优化工作流程。 常用快捷键部分应该列举常用的快捷键和相关操作,如快速打开文件、搜索、查看定义等。应该对不同功能区分操作系统进行说明。 实用插件和扩展部分应该介绍一些常见和有用的插件,如代码片段、代码格式化、调试器等。应该详细解释如何安装和使用这些插件。 调试功能部分应该详细介绍如何配置和使用调试器,包括设置断点、查看变量的值等。 版本控制部分应该介绍如何使用内置的版本控制工具,如Git,如何提交、推送和拉取代码等。 最后,代码片段部分应该教授如何创建和使用代码片段,以提高编码效率。 以上仅是对全网详细的VSCode教程的一些简要描述。当然,真正最详细的教程可能比这个更加全面和详细,具体内容可能还包括更多高级功能和技巧。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值