Gin源码解读(一) Gin是怎么跑起来的

764 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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
}