这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记。
在笔记服务(easy_note)中, api部分使用Gin框架处理收到的http请求. 然后我去网上查了一下, Gin框架是一种Go语言中常用的Web框架.为了更好地开发抖音项目,必须先学习一下Gin框架的原理和基本使用.
1. 什么是Gin框架
Gin 是一个用 Golang 语言编写的 Web 框架. 它具有类似 Martini 的 API,但性能比 Martini 快 40 倍.
2. Gin的特点
总接下来,Gin主要有以下特点:
-
快速
-
基于 Radix 树(一种更节省空间的 Trie 树结构)的路由,占用内存更少;
-
没有反射;
-
可预测的 API 性能。
-
-
内置路由器
- 开箱即用的路由器功能,不需要做任何配置即可使用。
-
支持中间件
- 比如日志,恢复等中间件;
-
崩溃处理
- Gin 可以捕获 HTTP 请求期间发生的panic并恢复它;
-
路由分组
-
支持通过路由群组来更好地组织路由,例如是否需要授权、设置 API 的版本等;
-
这些群组可以无限制地嵌套而不会降低性能。
-
-
中间件扩展
- 可以方便的定义新的中间件;
-
JSON验证
- 可以解析并验证 JSON 格式的请求数据(比如检查某个必须值是否存在).
3. Gin框架源码分析
默认的gin流程如下:
graph TD
gin.Default --> gin.New
gin.New --> |使用中间件:Use| Logger
gin.New --> 路由注册 --> r.Run
gin.New --> |使用中间件:Use| Recovery
r.Run 默认的监听端口是8080.
3.1 gin.Default
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
该函数创建默认的Engine实例, 然后引入Logger和Recovery中间件,这两个中间件的作用是:
-
Logger: 默认的日志处理, 会标准化和输出日志;
-
Recovery: 捕获异常并进行处理;
3.2 gin.New
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
TrustedPlatform: defaultPlatform,
UseRawPath: false,
RemoveExtraSlash: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
trees: make(methodTrees, 0, 9),
delims: render.Delims{Left: "{{", Right: "}}"},
secureJSONPrefix: "while(1);",
trustedProxies: []string{"0.0.0.0/0", "::/0"},
trustedCIDRs: defaultTrustedCIDRs,
}
engine.RouterGroup.engine = engine
engine.pool.New = func() any {
return engine.allocateContext()
}
return engine
}
可以看到该函数是对Engine实例进行初始化设置.
这些设置就是我们经常会使用到的,比如:
-
路由组;
-
是否自动重定向;
-
是否尝试修复当前请求路径;
-
是否对路径值进行转义处理;
-
等等.
3.3 路由注册
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()
}
通过查看源码,我们发现路由注册的流程如下:
-
计算路由的绝对路径, 和我们定义的路由相对路径组装在一起;
-
合并现有的和新注册的handlers,这样就构成了一个handler链;
-
将当前我们注册的路由规则添加到相应的路由树中.其中路由规则包含http方法,路由路径和handers.
3.4 r.Run
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
if engine.isUnsafeTrustedProxies() {
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine.Handler())
return
}
启动gin的流程为: 解析地址,然后调用http.ListenAndServe,将解析得到的地址和engine实例的handler传进去.
到了这里, Gin就可以对外提供http服务了~
4. 笔记服务中Gin的使用
func main() {
Init()
r := gin.New()
authMiddleware, _ := jwt.New(&jwt.GinJWTMiddleware{
// 省略
})
v1 := r.Group("/v1")
user1 := v1.Group("/user")
user1.POST("/login", authMiddleware.LoginHandler)
user1.POST("/register", handlers.Register)
note1 := v1.Group("/note")
note1.Use(authMiddleware.MiddlewareFunc())
note1.GET("/query", handlers.QueryNote)
note1.POST("", handlers.CreateNote)
note1.PUT("/:note_id", handlers.UpdateNote)
note1.DELETE("/:note_id", handlers.DeleteNote)
if err := http.ListenAndServe(":8080", r); err != nil {
klog.Fatal(err)
}
}
-
这里没有使用gin.Default(), 而是gin.New().这样的话就没有使用Logger和Recovery这两个中间件;
-
这里"/v1"为主路由组,然后下面有"/user"和"/note"这两个子路由组;
-
分别进行路由注册,根据路由转到相应的handler函数;
-
开启http.ListenAndServe, 从而提供http服务.