Gin源码分析一:引擎 Engine

在完成 Gin 的基本使用之后,就一直打算自习看一下 gin 的源码,结果一拖就拖到现在。现在有时间把这个坑慢慢补上。

gin/gin.go at master · gin-gonic/gin

大部分的 Go 的 HTTP 框架都是在重写路由部分,已实现更快的更准确的路由查找,减少路由解析过程中消耗的时间,来提高框架的处理速度。

Go 语言标准库处理 HTTP 请求的流程大致如下:

启动 HTTP 服务器:使用 http.ListenAndServe 或 http.ListenAndServeTLS 函数启动 HTTP 服务器。

处理请求:当服务器接收到 HTTP 请求时,它会使用与路径相对应的 http.Handler 实现处理请求。

调用处理程序:服务器会调用 ServeHTTP 方法,并将请求相关的信息作为参数传递给该方法。

路由匹配:在 ServeHTTP 方法中,通过比较请求的路径和已注册的路由,找到与请求匹配的路由。

调用处理函数:如果找到了匹配的路由,则调用与该路由相关的处理函数。

写入响应:处理函数通过 ResponseWriter 接口写入响应数据,以返回给客户端。

Go 语言标准库中的主要方法有:http.Handle 和 http.HandleFunc 用于注册路由和处理函数;

http.ListenAndServehttp.ListenAndServeTLS 用于启动 HTTP 服务器;以及 http.ResponseWriterhttp.Request 分别用于写入响应和处理请求。

在 gin 中,通过 Engine 这个对象进行管理,这个对象是实现了 ServeHTTP 这个方法。这个方法是 Go 标准库 net/http 中声明的一个接口方法。通过该接口来做到路由的转发。

1
2
3
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

Gin 收到相关的请求,都会统一调用 ServeHTTP 方法,该方法会将接收到的参数等进行处理,例如寻找合适的处理器(Handler),最后返回统一的处理结果。

这个是整个 HTTP 框架的关键,是处理 HTTP 请求的入口也是出口。

1
2
3
4
5
6
7
8
9
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	c := engine.pool.Get().(*Context)
	c.writermem.reset(w)
	c.Request = req
	c.reset()
	engine.handleHTTPRequest(c)
	engine.pool.Put(c)
}

找到了相关的入口,接下来可以分析整个处理流程,先看主要的对象 Engine

这也是我们使用 Gin 过程中第一使用的结构体。

这个结构体较为复杂,里面定义了大量的变量。根据我们之前看到的 ServeHTTP 方法中,首先使用到的变量有 pool

这里的 pool 使用的 sync.Pool 这个类型,主要是用来重复使用的 Context

这里直接从 pool 中取出 Context,并对 Context 的一些参数进行设置,最后调用 engine.handleHTTPRequest 方法。

engine.handleHTTPRequest 这个方法主要处理用户的 HTTP 请求,确定该请求的处理方法。简单的来说就:首先,获取请求的 HTTP 方法(如 GET 或 POST)和 URL 路径,并在需要时解码路径。然后,它搜索匹配该请求的处理树。如果找到了一个匹配的节点,它会将处理程序分配给请求的上下文(c),并写入 HTTP 响应头。如果未找到匹配的节点,则会写入 “405 Method Not Allowed” 或 “404 Not Found” 的错误响应。

这样基本就是一个简单的 http 请求的处理过程。在代码中可以看到很多事情其实是由上下文(Context) 进行处理的。

那么 Gin 框架是如何运行起来的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	r := gin.Default()

	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "hello world")
	})

	r.Run()
}

这里的三个方法基本就是 Gin 的主要方式,从这三个方法会看明白 Gin 的 engine 如何工作的。

Default() 方法中,主要还是使用了 New() 方法来创建。

New() 方法主要对 Engine 进行了初始化。

之后设置了两个中间件(Middleware),用于日志和异常的 Recovery。

都出事完成后返回 engine。

这个方法主要是在给定的地址上进行监听和创建相关的 HTTP 服务。

首先会判断是否为不安全的代理 isUnsafeTrustedProxies()

代码调用了 isTrustedProxy 方法,主要是对 trustedCIDRs 这个变量进行检查。这个变量在 New() 的时候就会进行初始化。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var defaultTrustedCIDRs = []*net.IPNet{
	{ // 0.0.0.0/0 (IPv4)
		IP:   net.IP{0x0, 0x0, 0x0, 0x0},
		Mask: net.IPMask{0x0, 0x0, 0x0, 0x0},
	},
	{ // ::/0 (IPv6)
		IP:   net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
		Mask: net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
	},
}

通过输入的 IP 会和 trustedCIDRs 进行比较,看看是否包含在其中。如果不包含就返回 false。默认的表达式为所有的 IPv4 和 IPv6 都符合。

另外通过 resolveAddress 方法进行地址解析,其实是指对端口解析。这个方法的输入为一个切片。如果用户不输入的时候,通过读取环境变量获取端口 os.Getenv("PORT")。如果无法获取环境变量的端口,那么就默认为 8080 。当用户输入具体的端口后,采用用户的输入。用户输入的数据过长,那么就会报错。

当上述都满足的时候,通过 http.ListenAndServe(address, engine.Handler()) 进行启动。

更多 http 标准库工作原理可以看 HTTP 标准库。

这样 Gin 框架就运行起来了。

相关内容