介绍
- gin 框架基于 httprouter 实现最重要的路由模块,采用类似字典树一样的数据结构来存储路由与handle方法的映射.也是框架高性能的原因,有兴趣的同学可以自行查阅
- 本文提供 在线思维导图 搭配文章看事半功倍
Engine
容器对象,整个框架的基础Engine.trees
负责存储路由和handle方法的映射,采用类似字典树的结构Engine.RouterGroup
,其中的Handlers存储着所有中间件Context
上下文对象,负责处理请求和回应
,其中的handlers
是存储处理请求时中间件和处理方法的
初始化容器
通过调用 gin.New()
方法来实例化Engine
. 虽然参数很多,但我们只需要注意 RouterGroup
,trees
和 engine.pool.New
即可
engine.pool.New
负责创建Context
对象,采用sync.Pool
减少频繁context实例化带来的资源消耗,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| 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
写入记录
1
2
3
4
5
6
7
8
9
10
11
12
13
| 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
中,接下来路由注册的时候就可以一起写入树节点中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| 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
增加节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| 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
对象就可以完成初始化并启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| 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()
处理请求 - 通过不停的移动下标递归,最后完成处理返回结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| 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中文文档
gin项目地址
httprouter
全网最详细的gin源码解析