Gin
是使用Go语言编写的高性能的web
服务框架,根据官方的测试,性能是httprouter
的40倍左右。要使用好这套框架呢,首先我们就得对这个框架的基本结构有所了解,所以我将从以下几个方面来对Gin
的源码进行解读。
Gin
是如何储存和映射URL
路径到相应的处理函数的Gin
中间件的设计思想及其实现Gin
是如何解析客户端发送请求中的参数的Gin
是如何将各类格式(JSON/XML/YAML
等)数据解析返回的Gin Github官方地址
在第一章中我们谈到Gin
的中间件是基于RouterGroup
数据结构实现的,这里我们再来回顾一下这个数据结构:
type HandlerFunc func(*Context)
type HandlersChain []HandlerFunc
type RouterGroup struct {
//中间件处理链
Handlers HandlersChain
//当前的路由基地址
basePath string
//Gin框架的核心引擎
engine *Engine
//当前
root bool
}
下面,我们以官方的示例代码来逐步研究其运行机制
func main() {
//创建一个不包含任何中间件的Engine
r := gin.New()
//添加日志中间件
r.Use(gin.Logger())
//添加错误回复重定向中间件
r.Use(gin.Recovery())
//以上这三部可以使用r := gin.Default()一步实现
//对/benchmark路由添加两个处理函数
r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
//创建路由分组
authorized := r.Group("/")
//使用AuthRequired中间件
authorized.Use(AuthRequired())
//这里的括号只是为了看起来整齐
{
//这里就是简单的路由信息配置
authorized.POST("/login", loginEndpoint)
authorized.POST("/submit", submitEndpoint)
authorized.POST("/read", readEndpoint)
testing := authorized.Group("testing")
//对/testing/analytics添加路由信息
testing.GET("/analytics", analyticsEndpoint)
}
r.Run(":8080")
}
func AuthRequired() gin.HandlerFunc {
return gin.BasicAuth(gin.Accounts{
"foo": "bar",
"austin": "1234",
"lena": "hello2",
"manu": "4321",
});
}
Endpoint
结尾的均是HandlerFunc
类型的处理函数
首先,我们来看添加中间件的函数
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
从这里我们可以看出Use
函数不过就是将我们添加的中间件函数存储到了RouterGroup
的Handlers
中,然后在添加搜索树节点时,会与我们的路由处理函数合并,这个我们在上一章中也提到过,就像下面这样。
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}
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()
}
基本上每个路由的最终匹配节点都会同时含有中间件处理函数和路由处理函数,就像上图其中的 ( 0 ) 、 ( 1 ) (0)、(1) (0)、(1)是我们添加的中间件函数,而 ( 2 ) (2) (2)则是我们为该路由添加的处理函数。所以每当请求被路由到某个节点时,都会遍历执行其下的handler
处理函数链。
由此可见,Gin
框架的中间件采用的是责任链模式
,而不是常见的代理模式
(Spring
为代表)来实现的。
除了上面的普通Use
函数来添加中间件外,Gin
还支持为某一指定路径添加一组路由中间件,使用如下函数实现:
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{
//合并已存在的处理函数
Handlers: group.combineHandlers(handlers),
//计算与当前路由分组的相对路径,默认有个根路由分组,基地址为"/"
basePath: group.calculateAbsolutePath(relativePath),
engine: group.engine,
}
}
通过创建一个路由分组,即可实现为特定路由前缀的节点应用中间件函数,而不是对整个根树所有终节点应用。例如:
//创建一个以"/"开头的路由分组
authorized := r.Group("/")
//对所有该分组下的路由应用AuthRequired中间件
authorized.Use(AuthRequired())
{
//为"/login"路由添加处理函数"loginEndpoint"
// 同时也会被应用AuthRequired中间件
authorized.POST("/login", loginEndpoint)
authorized.POST("/submit", submitEndpoint)
authorized.POST("/read", readEndpoint)
//以authorized为基础,创建一个子分组,
// 即匹配"/testing"开头的路由
testing := authorized.Group("testing")
//为"/testing/analytics"路由添加处理函数analyticsEndpoint
//同时也会被应用AuthRequired中间件
testing.GET("/analytics", analyticsEndpoint)
}
func AuthRequired() gin.HandlerFunc {
return gin.BasicAuth(gin.Accounts{
"foo": "bar",
"austin": "1234",
"lena": "hello2",
"manu": "4321",
});
}
在研读Gin
源码时,我发现如下代码:
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
if finalSize >= int(abortIndex) {
panic("too many handlers")
}
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
从上面的代码,可以看出每个节点的处理函数是有上限的,最多为abortIndex(63)
个,超出后就会抛出错误。特别要注意的是这里限制的个数是路由分组中所有中间件
的个数+我们自己编写的处理函数
的总个数,所以在对业务进行模块化时可能会抽离出很多中间件,这个时候一定要注意切不可分割得太细,否则可能会出现上述问题。