持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情
写在前面
我们今天就从下面这几行简单的代码中,探讨gin框架的底层实现
gin的底层是基于net/http包实现的,所以很多gin底层源码中涉及到了很多net/http的相关方法。
本文全部基于gin@v1.8.0进行讲解
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200,"pong")
})
_ = r.Run(":3000")
}
1. Run 函数底层实现
- gin/gin.go 文件
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.")
}
(1) address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
(2) err = http.ListenAndServe(address, engine.Handler())
return
}
这段代码还是比较容易看懂的。
address := resolveAddress(addr)将传入的addr进行判断,返回正确的端口。- 调用
http.ListenAndServe对这个端口进行监听,并将框架的信息引擎传进入。
然后让我们来看看这个ListenAndServe的具体实现
- net/http/server.go 文件
这个链接是基于TCP网络进行监听连接的,并且request和response都通过这个handler进行传递。
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
然后我们来看一下这个Handler对象是如何实现处理请求和响应的
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
这个Handler实现一个ServerHTTP的接口,来处理Response和Request,既然gin的Engine能和net/http包的Handler进行一个无缝连接,那么我们可以看看在这个gin包中,这个Engine是如何实现Handler()方法的。
接着我们来看一下这个gin的引擎对象 *Engine 实现的ServerHTTP方法
- gin/gin.go 563行
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) // 对上下文对象进行回收
}
那这个Engine的handleHTTPRequest() 方法究竟是怎么处理请求的呢?
- gin/gin.go 585行
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method // 获取请求的方法
rPath := c.Request.URL.Path // 获取URL请求地址
{...对请求地址进行判断处理}
t := engine.trees // 获取压缩前缀树数组,每个请求方法都有一颗radix树。
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
// 找到当前请求方式对应的radix树
continue
}
root := t[i].root // 得到树的根节点
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
// 根据请求路径获取匹配的redix树节点
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
// 如果这个路由处理器数组不为空,逐个调用处理器处理请求,响应给客户端
c.handlers = value.handlers
c.fullPath = value.fullPath
// 调用第一个处理器处理请求
c.Next()
c.writermem.WriteHeaderNow()
return
}
// {...请求后续处理,比如没有该方法之类的处理}
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
那你可能会对这个c.Next()感到疑惑,这个是如何对请求进行处理的呢?
- gin/context.go 170 行
func (c *Context) Next() {
c.index++
// 指向要执行的中间件,初始值为-1,对这个index进行自增操作
for c.index < int8(len(c.handlers)) {
// 遍历所有的处理器,一次调用他们来处理请求
c.handlers[c.index](c)
// 使用中间件处理请求,中间件可以改变c.index的值
c.index++
// 然后再进行自增
}
}
c.handlers 中是可以处理所有的路径请求,因为已经遍历完所有的c.index了,所以调用这个Next()就可以处理所有的命令。
然后我们看看这个Next()方法下面的另外两个方法IsAborted()和Abort()。
- IsAborted() 判断是否已经终止处理器调用
func (c *Context) IsAborted() bool {
return c.index >= abortIndex
}
- Abort() 终止处理器调用
func (c *Context) Abort() {
c.index = abortIndex
}