Gin Web框架记录
1.背景
Gin 是一个 golang 的 web 框架,封装比较优雅,API 友好,源码注释比较明确,具有快速灵活,容错方便等特点。它的主要作者是 Manu,Javier 和 Bo-Yi Wu,2016 年发布第一个版本,目前是最受欢迎的开源 Go 框架。
根据 Round 22 results - TechEmpower Framework Benchmarks 对现存的 web 框架的排名,Gin 框架以 95900 分的成绩位列第 185 名,与之相对的,业界常用的其他 web 框架比如 Spring 仅有 24082 分,位列第 381 名,可见 Gin 的效率远高于 spring 框架。
考虑到采用 go 进行开发时,其他排名靠前的 web 框架的生态并不如 Gin 完善,因此采用 Gin 是一个不错的选择。Gin 包含的组件和支持的功能如下:
组件 | 功能 |
---|---|
server | 作为 server,监听端口,接受请求 |
router | 路由和分组路由,可以把请求路由到对应的处理函数 |
middleware | 支持中间件,对外部发过来的 http 请求经过中间件处理,再给到对应的处理函数。例如 http 请求的日志记录、请求鉴权(比如校验 token)、CORS 支持、CSRF 校验等 |
template engine | 模板引擎,支持后端代码对 html 模板里的内容做渲染(render),返回给前端渲染好的 html |
Crash-free | 捕捉运行期处理 http 请求过程中的 panic 并且做 recover 操作,让服务一直可用 |
JSON validation | 解析和验证 request 里的 JSON 内容,比如字段必填等。 |
Error management | Gin 提供了一种简单的方式可以收集 http request 处理过程中的错误,最终中间件可以选择把这些错误写入到 log 文件、数据库或者发送到其它系统。 |
Middleware Extendtable | 支持用户自定义中间件 |
2.安装与项目创建
要使用 Gin 框架上手进行后端开发,可以遵循以下步骤:
安装 Go 语言支持(Gin 目前需要 1.13 以上版本的 Go 环境)
从 All releases - The Go Programming Language 下载对应系统的安装包(此处以 linux 为例),保存到服务器上,解压到/usr/local/go。
解压后,输入
sudo nano /etc/profile
打开配置文件,在文件末尾添加路径export PATH=$PATH:/usr/local/go/bin
,保存退出后使用source /etc/profile
使环境变量配置生效。安装 Gin 框架
在命令行输入
go get -u github.com/gin-gonic/gin
,在全局下载安装 Gin 框架拷贝初始模板与运行
在命令行输入
curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go > main.go
拷贝一个初始模板并使用go run main.go
运行即可
3.技术原理
3.1 程序启动流程
1 | package main |
结合上述 Gin 框架的主函数,我们可以梳理 Gin 后端程序的启动流程如下:
初始化 Gin:
gin.Default()
执行 Gin 的初始化过程,默认的初始化包含两个中间件1
2
3
4
5
6
7// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}- Logger:日志中间件,将 Gin 的启动与响应日志输出到控制台;
- Recovery:恢复中间件,将 Gin 遇到的无法处理的请求按 HTTP 500 状态码返回。
注册中间件:本例的
middleware.RegisterMiddleware(r)
用于将项目中开发的中间件注册到 Gin Engine 上;RegisterMiddleware
的定义如下:1
2
3
4
5
6
7
8
9
10package middleware
import (
"github.com/LearnGin/middleware/debug"
"github.com/gin-gonic/gin"
)
func RegisterMiddleware(r *gin.Engine) {
r.Use(debug.DebugMiddleWare())
}其中,
r.Use
负责将 gin.HandleFunc 类型函数注册为中间件。此处的debug.DebugMiddleWare()
是本例开发的一个简易的自定义中间件,用于在实际的事件处理前,输出详细的请求信息;在实际的事件处理后,输出结果状态码。注册事件处理:本例的
handler.RegisterHandler(r)
用于将项目中开发的对应于指定 URL 的事件处理函数注册到 Gin Engine 上;1
2
3
4
5
6
7
8
9
10
11package handler
import (
"github.com/LearnGin/handler/person"
"github.com/gin-gonic/gin"
)
func RegisterHandler(r *gin.Engine) {
r.Handle("GET", "/ping", PingHandler())
r.Handle("POST", "/person/create", person.CreatePersonHandler())
}gin.Engine 的
r.Handle
函数用于将事件处理函数注册到指定的 HTTP 方法 + 相对路径上。启动 Gin:
r.Run()
负责启动 Gin Engine,开始监听请求并提供 HTTP 服务。1
2
3
4
5
6
7
8
9
10
11
12
13func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
trustedCIDRs, err := engine.prepareTrustedCIDRs()
if err != nil {
return err
}
engine.trustedCIDRs = trustedCIDRs
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine)
return
}可以看到,Engine.Run 函数主要是:
- 解析监听地址传参;
- 启动监听与服务。
其中,最核心的监听与服务实质上是调用 Go 语言内置库 net/http 的
http.ListenAndServe
函数实现的。1
2
3
4func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}该函数实例化 Sever,并调用其
ListenAndServe
函数实现监听与服务功能。注意:此时,输入的 Gin Engine 对象以 Handler 接口的对象的形式被传入给了 net/http 库的 Server 对象,作为后续 Serve 对象处理网络请求时调用的函数。
3.2 Gin 框架路由原理
Gin 框架使用的是定制版本的 httprouter,其路由注册和查询功能是通过维护一个基数树(Radix Tree)来实现的。基数树又称为 PAT 位树(Patricia Trie or crit bit tree),本质上是对字典树(Trie Tree)的进一步压缩。对于基数树的每个节点,如果该节点是唯一的子树的话,就和父节点合并。关于前缀树和基数树,具体可以参考这里的介绍:树- 前缀树(Trie Tree),图解基数树(RadixTree)。
对于如下路由注册信息:
1 | r := gin.Default() |
Gin 会对 GET 方法给出一棵如下所示的路由树:
1 | Priority Path Handle |
其中,最右侧一列是指向路由节点对应的处理函数的指针,完整遍历该树即可输出对应于 GET 方法的路由表。类似于:post 的占位符和 s 的中间节点不存在对应处理函数。Gin 为每种请求方法单独维护路由树,并为各个数级别上的子节点分配优先级。一个节点的优先级在数值上等于其所有子节点中注册的句柄数,这样可以优先匹配被大多数路由路径包含的节点,同时使得最长的路径被优先匹配以抵消其较长的搜索时间。
Gin 维护的路由基数树数据结构如下:
1 | // tree.go |
3.3 Gin 框架中间件原理
在程序启动一节我们已经简要说明了如何向 Gin 引擎注册中间件,这里我们进一步说明中间件的注册和执行原理。
Engine 的 Use 方法调用的是 RouterGroup 的 Use 函数,另外加上了两个处理资源不存在情况的 404 和 405 错误处理中间件:
1 | func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { |
而在 RouterGroup 中的 Use 方法实际上是将中间件函数追加到了 group.Handlers 列表中:
1 | func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { |
注册路由时,Gin
会将路由处理函数和中间件全部组合成一个处理函数链(HandlersChain
),这是由
HandlerFunc
组成的切片:
1 | func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { |
中间件和路由处理函数的执行是通过 c.Next()
方法触发的,其中 c 代表 Context 对象。该方法循环遍历
HandlersChain
,依次执行每个 HandlerFunc
:
1 | func (c *Context) Next() { |
注册到函数链条的函数可以顺序执行,也可以嵌套执行。如下图所示:
在执行一个调用时,通过在该调用内部使用 c.Next 方法可以跳跃至后一个函数调用中;两个函数调用可以通过 c.Set 和 c.Get 两种方法传递数据,如下图所示:
4.示例
4.1 使用自定义中间件
1 | func Logger() gin.HandlerFunc { |
4.2 Restful后端服务
1 | package main |