gin源码-请求流程
参考资料
分析过程
先创建一个简单的demo,执行后的调用链路在这里
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(":8000")
}
运行后,请求一下127.0.0.1:8080/ping 最终的调用链路如下:
|-goroutine-1 created by runtime.main
|-main.main
|-github.com/gin-gonic/gin.Default
|-engine.Use(Logger(), Recovery()) // 注册了两个中间件
|-github.com/gin-gonic/gin.(*RouterGroup).handle
|-github.com/gin-gonic/gin.(*Engine).Run
|-github.com/gin-gonic/gin.resolveAddress
|-net/http.ListenAndServe(address, engine.Handler()) // 这里最终还是用的net/http包起的服务
// 不同的是serverMux使用的自定义的
// engine.Handler()
|-goroutine-7 created by net/http.(*Server).Serve.func3
|-net/http.(*conn).serve
|-net/http.(*conn).readRequest
|-net/http.serverHandler.ServeHTTP
|-github.com/gin-gonic/gin.(*Engine).ServeHTTP
|-sync.(*Pool).Get // 循环利用context对象内存
|-github.com/gin-gonic/gin.(*Engine).handleHTTPRequest(c)
|-root := engine.trees[i].root // 匹配对应请求类型的tree
|-value := root.getValue(rPath, c.params, ...) // 匹配对应路由的handler
|-c.handlers = value.handlers // 设置中间件
|-c.Next() // 执行第一个中间件
|-github.com/gin-gonic/gin.LoggerWithConfig.func1
|-github.com/gin-gonic/gin.CustomRecoveryWithWriter.func1
|-main.main.func1
|-github.com/gin-gonic/gin.(*Context).Query // 获取get参数
|-github.com/gin-gonic/gin.(*Context).GetQueryArray
|-net/url.(*URL).Query // 首次调用需要实例一个map对象
|-net/url.parseQuery
|-sync.(*Pool).Put
|-net/http.(*response).finishRequest
结合上一篇的文章,我们可以发现gin的服务器流程和net/http包的差不多,只是在响应连接的时候使用了自己的serverMux。gin号称自己是内存零分配路由,那么是否真的是零分配呢?
其实主要是看gin.(*Engine).handleHTTPRequest()在匹配路由时是否使用了额外内存,主要看root.getValue(rPath, c.params, c.skippedNodes, unescape)
,其中:
- rPath等于c.Request.URL.Path,这是在net/http创建的。
- c.params和c.skippedNodes是
gin.(*Engine).ServeHTTP()
用sync.Pool创建的,具体的New方法参考gin.(*Engine).allocateContext()
。所以也可以认为是零分配。
因此可以说gin在处理请求时,匹配路由的部分是零分配的。