1、数据如何在gin中流转
package main
import "github.com/gin-gonic/gin"
func main() {
// 创建 Gin Engine 实例
r := gin.Default()
// 设置请求 URI /ping 的路由及响应处理函数
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
// 启动 Web 服务,监听端口,等待 HTTP 请求到并生成响应
r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}
这段代码的大概流程:
r := gin.Default()初始化了相关的参数,创建Gin实例/ping将路由及处理handler注册到路由树中- 启动服务
r.Run()其实调用的是err = http.ListenAndServe(address, engine), gin其实利用了net/http的处理过程,将路由连接到http.Server启动并监听HTTP请求。
2、Engine
在整个gin框架中最重要的一个struct就是Engine, 它包含路由, 中间件, 相关配置信息等. Engine的代码主要就在gin.go中。
Engine中比较重要的几个属性, 其他的属性暂时全部省略掉
type Engine struct {
RouterGroup // 路由
pool sync.Pool // context pool
trees methodTrees // 路由树
// html template及其他相关属性先暂时忽略
}
engine有几个比较主要的函数:
New(), Default()
func New() *Engine {
// ...
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
// ...
trees: make(methodTrees, 0, 9),
}
engine.RouterGroup.engine = engine
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
return engine
}
New()主要干的事情:
- 初始化了
Engine - 将
RouterGroup的Handlers(数组)设置成nil,basePath设置成/ - 为了使用方便,
RouteGroup里面也有一个Engine指针, 这里将刚刚初始化的engine赋值给了RouterGroup的engine指针 - 为了防止频繁的
context GC造成效率的降低, 在Engine里使用了sync.Pool, 专门存储gin的Context
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
Default()跟New()几乎一模一样, 就是调用了gin内置的Logger(), Recovery()中间件。
Use()
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
Use()就是gin的引入中间件的入口了. 仔细分析这个函数, 不难发现Use()其实是在给RouteGroup引入中间件的。
addRoute()
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
...
root := engine.trees.get(method)
if root == nil {
root = new(node)
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
root.addRoute(path, handlers)
}
这段代码就是利用method, path, 将handlers注册到engine的trees中. 当 addRoute 时,先根据 method 找到对应的 tree (Engine.trees[i])。然后会比较 加入者 的 path 和 node 中的 path 相似的部分,相似的部分 作为 父结点,不同的部分作为 子结点。以 多叉树 的方式存储下来。
这里会把 URL 中的路由变量也当作字符串存入树中,因为相同 URL 他们的变量也是一样的。
3、Run系列函数
Run, RunTLS, RunUnix, RunFd这些函数其实都是最终在调用net/http的http服务。
gin.Run是net/http标准库http.ListenAndServe的简写,功能是将路由连接到http.Server启动并监听HTTP请求。
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
}
address := resolveAddress(addr)将传入的addr进行判断,返回正确的端口。- 调用
http.ListenAndServe对这个端口进行监听,并将框架的信息引擎传进入。
ServeHTTP
这个函数相当重要了, 主要有这个函数的存在, 才能将请求转到gin中, 使用gin的相关函数处理request请求。
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
// 创建上下文对象,注意这里是gin封装的Context,并不是go原生的Context!
// 这里用到了sync.Pool来进行内存的复用,防止频繁创建上下文,而导致性能的下降
c.writermem.reset(w)
c.Request = req
// 对请求进行赋值,并将这个req请求放到context的Request上下文中。
c.reset()
engine.handleHTTPRequest(c)// 处理请求
engine.pool.Put(c)// 对上下文对象进行回收
}
至此,终于我们看到了 gin.ServeHTTP 的全貌了
- 从 sync.pool 里面拿去一块内存
- 对这块内存做初始化工作,防止数据污染
- 处理请求 handleHTTPRequest
- 请求处理完成后,把这块内存归还到 sync.pool 中
本文正在参加技术专题18期-聊聊Go语言框架