前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go高级之从源码分析Gin框架的函数链

Go高级之从源码分析Gin框架的函数链

原创
作者头像
言志志
修改2023-11-06 20:27:39
9913
修改2023-11-06 20:27:39
举报
文章被收录于专栏:Go语言学习笔记Go语言学习笔记

前言

本文是探讨的是"Go高级之从源码分析Gin框架的函数链"

此文章是个人学习归纳的心得,未经允许,严禁转载,如有不对, 还望斧正, 感谢!

什么是Gin框架

Gin 是一个用 Go 语言开发的 Web 框架,提供类 Martini 的 API,并且由于GO语言的特性,然后性能更好。由于采用了httprouter库,它的性能比同类框架快了 40 倍左右。如果你需要一个高性能、高开发效率的框架,那么 Gin 就非常适合你,我很喜欢Go中文网对它的描述——晶莹剔透

Gin 提供了一系列的功能,包括但不限于路由管理、中间件、上下文参数传递等。它还支持多种数据格式,包括JSON、XML、HTML 等。此外,它还具有插件机制,可以方便地扩展框架的功能。

Gin 的主要优点如下:

  • 高性能:由于采用了httprouter库,Gin 的性能非常出色,比同类框架快了 40 倍左右。
  • 易用性:Gin 提供了一系列的内置功能,使你可以轻松地实现 Web 开发。
  • 扩展性:Gin 具有插件机制,可以方便地扩展框架的功能。

其实就是一句话,Gin框架对新手很友好

Gin框架的核心

要想用一篇文章讲完一个框架,很有难度,我将分开来讲,主要是从方法链,Gin的九颗方法树,上下文处理来讲

初识Gin核心理念

Gin框架的实现原理主要涉及以下几个核心部分:

  1. 路由匹配与处理:Gin使用基数树(Radix Tree)来管理路由,将路由路径分解为多个节点,通过匹配路径的前缀来快速找到对应的路由处理函数。当有新的路由注册时,Gin会根据路径构建对应的节点,并将处理函数与该节点绑定。在请求到来时,Gin会从根节点开始遍历路由树,根据请求路径匹配到对应的处理函数进行执行。
  2. 中间件机制:Gin的中间件机制是通过方法链实现的。每个中间件都是一个函数,它接收一个上下文对象(Context)和一个函数参数(next),并在执行过程中可以处理请求和响应,然后通过调用next()函数将控制权交给下一个中间件。这样,多个中间件可以形成一个链式调用的过程,依次对请求进行处理和控制。
  3. 上下文管理:Gin的上下文对象(Context)封装了一次HTTP请求的上下文信息,包括请求参数、请求头、响应内容等。在处理请求过程中,可以通过上下文对象获取和设置这些信息。Gin通过将上下文对象作为参数传递给中间件和路由处理函数,实现了在这些函数之间共享数据和状态的能力。
  4. 异常处理:Gin框架内置了对异常的处理机制。当发生异常时,Gin会捕获异常并返回一个合适的错误响应。同时,Gin还提供了一些辅助方法,如Abort()和AbortWithStatus(),用于在处理过程中终止请求并返回特定的错误响应。
  5. 并发处理:Gin框架使用goroutine来实现并发处理请求。每个请求都会在独立的goroutine中执行,这样可以提高服务器的并发处理能力。

细品函数链

Engine结构体

gin框架的核心就是Engine结构体,我给一些字段加了必要的注释,我们一开始的

gin.Default()或者gin.New()其实都是实例化一个Engine结构体

代码语言:go
复制
type Engine struct {
	RouterGroup

	// RedirectTrailingSlash启用自动重定向,如果当前路由无法匹配但存在没有尾部斜杠的路径的处理程序。
	// 例如,如果请求/foo/,但只有/foo的路由存在,那么对于GET请求,客户端将被重定向到/foo,并返回301状态码,对于其他请求方法,返回307状态码。
	RedirectTrailingSlash bool

	// RedirectFixedPath如果启用,路由器将尝试修复当前请求路径,如果没有为其注册处理程序。
	// 首先,会删除多余的路径元素,如../或//。
	// 然后,路由器对清理后的路径进行不区分大小写的查找。
	// 如果能找到此路由的处理程序,则路由器将使用状态码301对GET请求进行重定向,对于其他请求方法,返回307状态码。
	// 例如,/FOO和/..//Foo可能会被重定向到/foo。
	// RedirectTrailingSlash与此选项无关。
	RedirectFixedPath bool

	// HandleMethodNotAllowed如果启用,路由器将检查当前路由是否允许使用其他方法,
	// 如果当前请求无法路由。
	// 如果是这种情况,请求将以“Method Not Allowed”响应,并返回HTTP状态码405。
	// 如果没有其他方法被允许,则将请求委托给NotFound处理程序。
	HandleMethodNotAllowed bool

	// ForwardedByClientIP如果启用,将从请求的头部中解析与`(*gin.Engine).RemoteIPHeaders`匹配的客户端IP。
	// 如果未获取到IP,则回退到从`(*gin.Context).Request.RemoteAddr`获取的IP。
	ForwardedByClientIP bool

	// AppEngine已废弃。
	// Deprecated: 使用值为gin.PlatformGoogleAppEngine的`TrustedPlatform`代替
	// #726 #755 如果启用,将信任以“X-AppEngine…”开头的某些头部,以更好地与该PaaS集成。
	AppEngine bool

	// UseRawPath如果启用,将使用url.RawPath查找参数。
	UseRawPath bool

	// UnescapePathValues如果为true,则解码路径值。
	// 如果UseRawPath为false(默认情况下),则UnescapePathValues实际上为true,
	// 因为将使用url.Path,而url.Path已经解码。
	UnescapePathValues bool

	// RemoveExtraSlash即使有额外的斜杠,也可以从URL中解析参数。
	// 参见PR#1817和问题#1644
	RemoveExtraSlash bool

	// RemoteIPHeaders用于在`(*gin.Engine).ForwardedByClientIP`为`true`且
	// `(*gin.Context).Request.RemoteAddr`匹配`(*gin.Engine).SetTrustedProxies()`定义的网络源列表之一时,
	// 获取客户端IP的头部列表。
	RemoteIPHeaders []string

	// TrustedPlatform如果设置为值为gin.Platform*的常数,将信任该平台设置的头部,例如用于确定客户端IP
	TrustedPlatform string

	// MaxMultipartMemory是传递给http.Request的ParseMultipartForm方法调用的'maxMemory'参数的值。
	MaxMultipartMemory int64

	// UseH2C启用h2c支持。
	UseH2C bool

	// ContextWithFallback在Context.Request.Context()不为nil时,启用回退Context.Deadline(),Context.Done(),Context.Err()和Context.Value()。
	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
}

在这个结构体里面,还内嵌了一个很重要的结构体

RouterGroup,这种内嵌的效果,其实类似于其他语言中的继承,Engine结构体得到了,RouterGroup结构体的属性和方法

RouterGroup结构体

RouterGroup结构体的源码如下

代码语言:go
复制
type RouterGroup struct {
   Handlers HandlersChain   //存储将要执行的函数,包括路由中间件和路由函数
   basePath string          // 路径
   engine   *Engine         // 指向根结构体
   root     bool            // 标志是不是根结构体
}

第一个字段其实就是方法链,用来储存方法,详细结构如下

代码语言:txt
复制
// 起别名
type HandlerFunc func(*Context)

// 定义函数切片类型
type HandlersChain []HandlerFunc

到这里你肯定还是懵的,不急,接下来我们再分析一下RouterGroup结构体身上的几个常用的方法,相信到后面,你应该也能明白,大概是怎么一回事

Use方法使用中间件

先是Use方法,我们一般使用Use函数来使用中间件函数,看下面的代码,相信你也明白了,其实Use一个中间件,就是把这个中间件函数放到了RouterGroup中的Handlers函数链里面去了,通过使用append函数,来添加。

代码语言:txt
复制
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
   group.Handlers = append(group.Handlers, middleware...)
   return group.returnObj()
}

Group方法划分路由组

然后是Group方法,这个方法,我们一般用来划分路由组,就比如说 http://192.168.127.1:9090/demo/one

和http://192.168.127.1:9090/demo/two 我们可以先用Group方法,划分一个demo路由组出来,然后再进行相关的操作。

这个方法,新建了一个RouterGroup结构体,调用了两个函数group.combineHandlers()group.calculateAbsolutePath()函数,然后返回了这个新建的RouterGroup结构体的地址

代码语言:txt
复制
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
   return &RouterGroup{
      Handlers: group.combineHandlers(handlers), //该字段存储将要执行的函数,包括路由中间件和路由函数
      basePath: group.calculateAbsolutePath(relativePath), // 路径
      engine:   group.engine,   // 指向根结构体
   }
}
group.combineHandlers()

我们再看看group.combineHandlers()函数,其实就是将新使用的中间件塞入函数链,这个函数是内置,没有开放给外界使用

代码语言:go
复制
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
}

来看看具体解释

该函数的目的是将当前RouterGroup的中间件处理函数和传入的handlers合并成一个新的中间件处理函数链。

函数接收一个handlers参数,它是一个HandlersChain类型,表示一组中间件处理函数。HandlersChain是一个自定义类型,它实际上是一个切片,存储了多个中间件处理函数。

函数首先计算了合并后的中间件处理函数链的长度,通过将当前RouterGroup中已有中间件处理函数的数量和传入的handlers的数量相加得到。然后,使用assert1函数进行断言,确保合并后的长度没有超过abortIndex的最大值。abortIndex是一个常量,用于限制中间件处理函数链的最大长度。

接下来,函数创建了一个长度为finalSize的切片mergedHandlers,用于存储合并后的中间件处理函数链。然后,通过copy函数将当前RouterGroup的中间件处理函数复制到mergedHandlers的起始位置,再将传入的handlers复制到mergedHandlers的末尾位置。

最后,函数返回合并后的中间件处理函数链mergedHandlers

group.calculateAbsolutePath()函数
代码语言:go
复制
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
   return joinPaths(group.basePath, relativePath)
}

group.calculateAbsolutePath()函数,用于计算绝对路径。它接收一个相对路径作为参数,然后调用joinPaths函数将相对路径与路由组的基本路径拼接在一起,得到一个完整的绝对路径。

joinPaths函数接收一个绝对路径和一个相对路径作为参数,将它们拼接在一起并返回拼接后的路径。首先,函数会检查相对路径是否为空,如果为空,则直接返回绝对路径。接下来,函数使用path.Join函数将绝对路径和相对路径拼接在一起,得到最终路径。然后,函数会检查相对路径的最后一个字符是否为'/',如果是,并且最终路径的最后一个字符不是'/',则在最终路径的末尾添加'/'。最后,函数返回最终路径。

这段代码的作用是将路由组的基本路径和相对路径拼接在一起,得到一个完整的绝对路径。它处理了相对路径为空和最终路径的最后一个字符的情况,确保返回的路径是正确的。

然后介绍的就是Next方法,这个方法常用在中间件里面,当我们在一个中间件中需要执行后面的中间件,我们就可以使用Next函数,如下,其实就是执行了函数链中的下一个函数,对了,我们通过`gin.Default()

得到的默认的日志中间件,其实就是用了Next`方法

代码语言:txt
复制
func (c *Context) Next() {
   c.index++
   for c.index < int8(len(c.handlers)) {
      c.handlers[c.index](c)
      c.index++
   }
}

当路由被访问的时候,然后就会依次执行函数链里面的函数。

总结

    1. Enginegin的核心结构体,它包含了RouterGroup结构体,实现了路由的注册和中间件的添加等功能。RouterGroup用来表示一个路由组,它包含了中间件处理函数链Handlers、路径basePath、引擎指针engine等字段。RouterGroup实现了路由组相关的添加路由,添加中间件等操作。
    1. 当路由被匹配时,会依次调用Handlers链中的中间件函数和路由处理函数。
    1. EngineRouterGroup实现了分组路由和中间件的机制,形成了路由注册和中间件添加的链式调用风格式, gin通过这种机制,提供了强大的路由与中间件功能,形成了简洁的API风格。

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 什么是Gin框架
  • Gin框架的核心
    • 初识Gin核心理念
    • 细品函数链
      • Engine结构体
        • RouterGroup结构体
          • Use方法使用中间件
          • Group方法划分路由组
      • 总结
      相关产品与服务
      消息队列 TDMQ
      消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
      http://www.vxiaotou.com