前言
今天是我 golang 框架阅读系列第三篇文章,今天我们主要看看 gin 的框架执行流程。关于 golang 框架生命周期 源码阅读下面是我的计划:
计划 | 状态 |
---|---|
Go 框架解析 -beego | done |
Go 框架解析 -iris | done |
Go 框架解析 -gin | done |
Go 框架解析 -echo | doing |
Go 框架解析 -revel | doing |
Go 框架解析 -Martini | doing |
再完成各个 golang 框架生命周期的解析之后,我会计划对这几个框架的优略进行一个系列分析,由于业内大多都是性能分析的比较多,我可能会更侧重于以下维度:
- 框架设计
- 路由算法
第一波我们主要把重点放在 框架设计 上面。
安装
上次阅读 iris 我们使用的 glide 安装的,今天我们安装 gin 尝试下使用 gomod, 具体步骤如下。
使用 go mod 安装:
// 初始化 go.mod 文件
go mod init gin-code-read
// 安装 gin
go get github.com/gin-gonic/gin
// 复制依赖到 vendor 目录
go mod vendor
启动一个简单的 gin http 服务:
package main
import "github.com/gin-gonic/gin"
func main() {r := gin.Default()
r.GET("ping", func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong"})
})
r.Run()}
看上面的启动代码是不是很熟悉,和 iris 很像是吧,同样的 Default
方法。
gin 的生命周期
看完 gin 框架流程我有大致如下几个感触:
- gin 是我目前看过的这三个 go 框架里最简洁的框架
- gin 和 iris 在框架设计存在风格一致的地方,例如注册中间件、handle 的执行
总之,目前就一个感受:
Gin 是我认为的一个 GO 框架应该有的样子
下图就是我对整个 Gin 框架生命周期的输出,由于图片过大存在平台压缩的可能,建议大家直接查看原图链接。
访问图片源地址查看大图 http://cdn.tigerb.cn/20190704…
原图查看链接: http://cdn.tigerb.cn/20190704…
关键代码解析
// 获取一个 gin 框架实例
gin.Default()
⬇️
// 具体的 Default 方法
func Default() *Engine {
// 调试模式日志输出
// ???? 很不错的设计
debugPrintWARNINGDefault()
// 创建一个 gin 框架实例
engine := New()
// 是不是很眼熟 和 iris 里注册中间件的方式一致
// 不过比 iris 好的是支持多参数 iris 则是得调用多次
engine.Use(Logger(), Recovery())
return engine
}
⬇️
// 创建一个 gin 框架实例 具体方法
func New() *Engine {
// 调试模式日志输出
debugPrintWARNINGNew()
// 先插入一个小话题,可能好多人都在想为什么叫 gin 呢?// 哈哈,这个框架实例的结构体实际命名的 Engine, 很明显 gin 就是一个很个性的简称了,是不是真相大白了。// 初始化一个 Engine 实例
engine := &Engine{
// 路由组
// 给框架实例绑定上一个路由组
RouterGroup: RouterGroup{
// engine.Use 注册的中间方法到这里
Handlers: nil,
basePath: "/",
// 是否是路由根节点
root: true,
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
AppEngine: defaultAppEngine,
UseRawPath: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
// 路由树
// 我们的路由最终注册到了这里
trees: make(methodTrees, 0, 9),
delims: render.Delims{Left: "{{", Right: "}}"},
secureJsonPrefix: "while(1);",
}
// RouterGroup 绑定 engine 自身的实例
// 不太明白为何如此设计
// 职责分明么?engine.RouterGroup.engine = engine
// 绑定从实例池获取上下文的闭包方法
engine.pool.New = func() interface{} {
// 获取一个 Context 实例
return engine.allocateContext()}
// 返回框架实例
return engine
}
⬇️
// 注册日志 &goroutin panic 捕获中间件
engine.Use(Logger(), Recovery())
⬇️
// 具体的注册中间件的方法
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
// 上面 是一个 engine 框架实例初始化的关键代码
// 我们基本看完了
// --------------router--------------
// 接下来 开始看路由注册部分
// 注册 GET 请求路由
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
// 往路由组内 注册 GET 请求路由
return group.handle("GET", relativePath, handlers)
}
⬇️
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {absolutePath := group.calculateAbsolutePath(relativePath)
// 把中间件的 handle 和该路由的 handle 合并
handlers = group.combineHandlers(handlers)
// 注册一个 GET 集合的路由
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()}
⬇️
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)
// 检查有没有对应 method 集合的路由
root := engine.trees.get(method)
if root == nil {
// 没有 创建一个新的路由节点
root = new(node)
// 添加该 method 的路由 tree 到当前的路由到路由树里
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
// 添加路由
root.addRoute(path, handlers)
}
⬇️
// 很关键
// 路由树节点
type node struct {
// 路由 path
path string
indices string
// 子路由节点
children []*node
// 所有的 handle 构成一个链
handlers HandlersChain
priority uint32
nType nodeType
maxParams uint8
wildChild bool
}
// 上面
// 我们基本看完了
// --------------http server--------------
// 接下来 开始看 gin 如何启动的 http server
func (engine *Engine) Run(addr ...string) (err error) {defer func() {debugPrintError(err) }()
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
// 执行 http 包的 ListenAndServe 方法 启动路由
// engine 实现了 http.Handler 接口 所以在这里作为参数传参进去
// 后面我们再看 engine.ServeHTTP 的具体逻辑
err = http.ListenAndServe(address, engine)
return
}
⬇️
// engine 自身就实现了 Handler 接口
type Handler interface {ServeHTTP(ResponseWriter, *Request)
}
⬇️
// 下面就是网络相关了
// 监听 IP+ 端口
ln, err := net.Listen("tcp", addr)
⬇️
// 上面执行完了监听
// 接着就是 Serve
srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
⬇️
// Accept 请求
rw, e := l.Accept()
⬇️
// 使用 goroutine 去处理一个请求
// 最终就执行的是 engine 的 ServeHTTP 方法
go c.serve(ctx)
// 上面服务已经启动起来了
// --------------handle request--------------
// 接着我们来看看 engine 的 ServeHTTP 方法的具体内容
// engine 实现 http.Handler 接口 ServeHTTP 的具体方法
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 获取一个上下文实例
// 从实例池获取 性能高
c := engine.pool.Get().(*Context)
// 重置获取到的上下文实例的 http.ResponseWriter
c.writermem.reset(w)
// 重置获取到的上下文实例 *http.Request
c.Request = req
// 重置获取到的上下文实例的其他属性
c.reset()
// 实际处理请求的地方
// 传递当前的上下文
engine.handleHTTPRequest(c)
// 归还上下文实例
engine.pool.Put(c)
}
⬇️
// 具体执行路由的方法
engine.handleHTTPRequest(c)
⬇️
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
// 这里寻找当前请求 method 的路由树节点
// 我在想这里为啥不用 map 呢?// 虽说也遍历不了几次
if t[i].method != httpMethod {continue}
// 找到节点
root := t[i].root
// 很关键的地方
// 寻找当前请求的路由
handlers, params, tsr := root.getValue(path, c.Params, unescape)
if handlers != nil {
// 把找到的 handles 赋值给上下文
c.handlers = handlers
// 把找到的入参赋值给上下文
c.Params = params
// 执行 handle
c.Next()
// 处理响应内容
c.writermem.WriteHeaderNow()
return
}
...
}
// 方法树结构体
type methodTree struct {
// HTTP Method
method string
// 当前 HTTP Method 的路由节点
root *node
}
// 方法树集合
type methodTrees []methodTree
⬇️
// 执行 handle
func (c *Context) Next() {
// 上下文处理之后 c.index 被执为 -1
c.index++
for s := int8(len(c.handlers)); c.index < s; c.index++ {// 遍历执行所有 handle(其实就是中间件 + 路由 handle)
// 首先感觉这里的设计又是似曾相识 iris 不是也是这样么 不懂了 哈哈
// 其次感觉这里设计的很一般 遍历?多无聊,这里多么适合「责任链模式」// 之后给大家带来关于这个 handle 执行的「责任链模式」的设计
c.handlers[c.index](c)
}
}
// Context 的重置方法
func (c *Context) reset() {
c.Writer = &c.writermem
c.Params = c.Params[0:0]
c.handlers = nil
// 很关键 注意这里是 - 1 哦
c.index = -1
c.Keys = nil
c.Errors = c.Errors[0:0]
c.Accepted = nil
}
结语
最后我们再简单的回顾下上面的流程, 从上图看来,是不是相对于 iris 简单了好多。
《Golang 框架解析》系列文章链接如下:
- Golang 框架解析 -gin
- Golang 框架解析 -iris
- Golang 框架解析 -beego