我们使用下面代码示例来讲解Run
起来的经过。
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
user := r.Group("/user")
user.GET("/info",func(c *gin.Context) {
c.JSON(200, gin.H{
"user": "游客",
})
})
user := r.Group("/admin")
user.Use(JWTAuth)
user.GET("/info",func(c *gin.Context) {
c.JSON(200, gin.H{
"user": "水洗牛仔裤",
})
})
r.Run()
}
调用Default
函数初始化了Engine
结构体
type Engine struct {
RouterGroup
trees methodTrees
//省略了很多字段
}
// RouterGroup用于内部配置路由器,RouterGroup与前缀和处理程序(中间件)数组相关联。
type RouterGroup struct {
Handlers HandlersChain
basePath string
engine *Engine
root bool
}
RouterGroup
是用来保存中间件函数的,示例代码中的Default
函数使用的两个函数就被保存在GouterGroup
的Hanlers
中。
这个函数,示例上没有,我们补充一下:
//创建一个新的路由器组。您应该添加所有具有公共中间件或相同路径前缀的路由。
//例如,可以将使用公共中间件进行授权的所有路由分组。
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{
Handlers: group.combineHandlers(handlers),
basePath: group.calculateAbsolutePath(relativePath),
engine: group.engine,
}
}
他是实现路由分组的函数,basePath
这个字段就是用来储存路径的,handlers
储存中间件函数。
**注意一下:**使用Group
函数会返回一个新的RouterGroup
但是engine
还是指向之前的那一个。
调用r.Get
函数时会调用Engine
中RouterGroup
的方法,将RouterGroup
的路径和中间件函数与r.Get
中的路径与处理函数结合。
这个时候这个路由的路径是完整的,并且访问路由所需要的调用的函数也是完整的。
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()
}
最后调用addRoute
方法通过前缀树路径匹配,将节点添加到前缀树上。
type methodTree struct {
method string
root *node
}
type methodTrees []methodTree
type node struct {
path string
indices string
wildChild bool
nType nodeType
priority uint32
children []*node // child nodes, at most 1 :param style node at the end of the array
handlers HandlersChain
fullPath string
}
Gin的路由树是一个切片+前缀树组成的。可以理解为一个方法一颗树,如Get方法对应Get前缀树,
Post方法对应Post前缀树,这样处理让Gin的路由简单的实现了**RESTful API
。**
这个例子生成的前缀树如下图:
user下的info中有三个函数:Logger,Recovery和处理函数。
admin下的info有四个函数:Logger,Recovery,JWTAuth和处理函数。
Run函数的作用是调用http.ListenAndServe去监听设置的地址端口。
这里咱们用一个思维导图去梳理Run干了什么事:
net.Listen
创建一个网络监听器。
l.Accept
创建一个网络连接器。
c.Serve(ctx)
开启一个客户端连接。在for循环中,每一个连接都会创建一个新的协程去处理。
serverHandler{c.server}.ServeHTTP(w, w.req)
这个结构是创建了一个serverHandler结构体设置字段值。
handler.ServeHTTP(rw, req)
回到Gin框架,初始化gin.Context
,获取http.ResponseWriter
和
*http.Request
的数据。
handleHTTPRequest
获取对应的方法树。这个匹配的过程和前缀树插入的过程很相似,可以查查看看。
getValue
由路径获取方法前缀树上对应的节点。
[c.Next]
调用节点上函数链的第一个函数。经过链式调用完成所有的工作。
连接的过程存在go的Context贯穿整个过程。在此没有解释,后续会写一篇关于Context的文章。