Golang之Gin框架源码解读——第二章

Gin是使用Go语言编写的高性能的web服务框架,根据官方的测试,性能是httprouter的40倍左右。要使用好这套框架呢,首先我们就得对这个框架的基本结构有所了解,所以我将从以下几个方面来对Gin的源码进行解读。

  • 第一章:Gin是如何储存和映射URL路径到相应的处理函数的
  • 第二章:Gin中间件的设计思想及其实现
  • 第三章:Gin是如何解析客户端发送请求中的参数的
  • 第四章:Gin是如何将各类格式(JSON/XML/YAML等)数据解析返回的

Gin Github官方地址

Gin中间件的设计思想及其实现

在第一章中我们谈到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函数不过就是将我们添加的中间件函数存储到了RouterGroupHandlers中,然后在添加搜索树节点时,会与我们的路由处理函数合并,这个我们在上一章中也提到过,就像下面这样。

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

在树的节点中就像下图这样:
Golang之Gin框架源码解读——第二章_第1张图片

中间件实现方式

基本上每个路由的最终匹配节点都会同时含有中间件处理函数和路由处理函数,就像上图其中的 ( 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)个,超出后就会抛出错误。特别要注意的是这里限制的个数是路由分组中所有中间件的个数+我们自己编写的处理函数的总个数,所以在对业务进行模块化时可能会抽离出很多中间件,这个时候一定要注意切不可分割得太细,否则可能会出现上述问题。

你可能感兴趣的:(Golang,#,Gin源码解读,中间件,go,web)