内容纲要
目的
从最简单的 HelloWorld(实际上并没有输出 HelloWorld 😁) 入手,分析 Gin 的执行过程。
HelloWorld Demo
package main
import "github.com/gin-gonic/gin"
func main() {
engine := gin.Default()
engine.Run() // 监听并在 0.0.0.0:8080 上启动服务
}
分析
gin.Default()
// gin.Default() 代码
// 返回一个启用 Recovery 中间件 和 Logger 中间件的 Engine 实例
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
注释分析
- 分析三个英文名词
- Recovery:根据名词只知道是一个中间件,不知道具体作用;先忽略
- Logger:从名词可以看出,应该是跟日志相关的中间件;先不深入研究
- Engine:行业通常翻译为“引擎”,从名词可以大概看出,这个东西应该是整个 Gin 的核心;是需要重点分析的部分
代码初步分析
- 函数名被注释解释的很清楚了:返回 Engine 实例的指针
- 第 5 行
- debugPrintWARNINGDefault():目测是一个 debug 打印的函数(进入代码后可以看到确实是,功能为判断 Gin 的最小支持的 Go 版本)
- 第 6 行
- New(Go 的常规写法) 一个实例(跟踪 New() 可以看到是 Engine 结构体的实例化方法)
- 第 7 行
- 根据 Use 函数的名称结合之前的注释,可以推断出这个函数的目的是把 Recovery 中间件和 Logger 中间件插入到 Engine 中(Use 函数的参数是一个可变长参数,可以判断这个函数可以接收无数个中间件,进一步判断出 Gin 可能有好多个中间件,甚至可以自己开发中间件)
- 返回行返回实例指针
由之前初步分析,我们可以得出比较关键的信息:
- Engine
Engine 结构体预览
这部分较长,只需要初步看一遍,对各个变量有一定的认识就行(混眼熟)。
// New 实例的默认值:
// - RedirectTrailingSlash: true
// - RedirectFixedPath: false
// - HandleMethodNotAllowed: false
// - ForwardedByClientIP: true
// - UseRawPath: false
// - UnescapePathValues: true
type Engine struct {
// 路由组:分组组织管理路由( 这里相当于 Engine 继承 RouterGroup)
RouterGroup
// URL 路径末尾带或不带‘/’的请求,如果路由表中匹配不到对应的路径,则值为:
// true:自动重定向到路由表中带或不带‘/’的路径
// example:
// [路由:/foo] [请求:/foo 或 /foo/] [结果:都指向 /foo]
// [路由:/foo/] [请求:/foo 或 /foo/] [结果:都指向 /foo/]
// false:不自动重定向
RedirectTrailingSlash bool
// 当路径在路由表中匹配不到时,值为:
// true:尝试修复路径
// example:
// [路由:/foo] [请求:../../foo] [结果:指向 /foo]
// [路由:/foo/] [请求:../../foo] [结果:指向 /foo/]
// false:不修复
RedirectFixedPath bool
// 匹配不到对应的路由时,是否允许检查其它 Method 方法,值为:
// true:允许检查(返回 405:Method not allowed)
// example:
// [路由:GET /foo] [请求:PUT /foo] [结果:返回 405:Method not allowed]
// false:不检查(返回 404:page not found)
// example:
// [路由:GET /foo] [请求:PUT /foo] [结果:返回 404:Page not found]
HandleMethodNotAllowed bool
// 是否转发客户端 IP
// true:转发
// false:不转发
ForwardedByClientIP bool
// 是否在请求头中插入 X-AppEngine... (跟 PaaS 结合)
// true:插入
// false:不插入
AppEngine bool
// 是否使用 url.RawPath 查找参数
// true:使用
// false:不使用(默认使用 url.Path)
UseRawPath bool
// 请求是否转义
// true:不转义(保留 url.Path)(如果 UseRawPath == false:UnescapePathValues 为 true)
// false:转义
UnescapePathValues bool
// maxMemory 的值(http.Request 的 ParseMultiparForm 调用时的参数)
MaxMultipartMemory int64
// 是否可用从有额外‘/’的 URL 中解析参数
// true:可以
// false:不可以
RemoveExtraSlash bool
// 表示一组用于 HTML 模板渲染的分隔符(见 html/template 库)
delims render.Delims
// 在 context.SecureJSON 中使用的前缀
secureJsonPrefix string
// HTMLRender 接口(用于渲染 HTMLProduction、HTMLDebug 的模板)
HTMLRender render.HTMLRender
// 用于映射函数名和函数(在 text/template 包中)
FuncMap template.FuncMap
// 找不到路由?(留坑待补)
allNoRoute HandlersChain
// 找不到请发方法?(留坑待补)
allNoMethod HandlersChain
// 找不到路由?(留坑待补)
noRoute HandlersChain
// 找不到请发方法?(留坑待补)
noMethod HandlersChain
// (留坑待补)
pool sync.Pool
// methodtrees 包含一组请求方法和 Node(Node 管理 Path 节点树) 指针的结构体
trees methodTrees
}
engine.Run()
看过 Engine 后,我们对它的一些变量有了初步的认识,也能从中推断出一些用处。然后我们再把视线转回 HelloWorld Demo 的第 12 行来。
// 将 Engine 的路由挂载到 http.Server 上,并开起监听,等待 HTTP 请求
// addr:监听地址
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine)
return
}
注释分析
- Engine
- 即之前看过的结构体
- 从函数声明可以看出,Run 是 Engine 的一个函数方法
- 调用时可以自定义监听地址(127.0.0.1、0.0.0.0 等)
- 函数调用失败则返回错误
- http.Server
- Go 自带的 HTTP 服务库
- 只提供一些比较底层的调用(使用不友好)
- 推断出 Gin 基于 Go 自带的 http 服务库开发(实际上人家自己也说过)
- 监听
- Run 函数执行成功后处于监听状态
- 等待 HTTP 请求
- Run 执行成功后等待 HTTP 请求
代码初步分析
- 第 4 行
- 优秀的写法:函数结束时输出错误信息(如果有错误的话)
- 第 6 行
- 获取最终监听地址及端口
- addr 长度为 0 时:获取环境变量 PORT(没环境变量则默认使用 8080 端口),返回 “:${PORT}”
- addr 长度为 1 时:返回 addr[0]
- 其余长度引发 panic
- 获取最终监听地址及端口
- 第 7 行
- 输出最终监听地址端口
- 第 8 行(划重点,这里要考的 ☆☆☆☆☆)
- 调用 Go 的 http.Server(什么时候有空再更深入的看看),设置 Handler 为 engine
- Handler 是一个 interface
- 有一个 ServeHTTP(ResponseWriter, *Request) 接口
- Engine 实现了 ServeHTTP 接口
- Handler 是一个 interface
- 监听并提供服务
- 在 http.Server 接收到请求经过一系列处理后,会最终调用设置为 Handler 的 Engine 的 ServerHTTP 接口,并传递请求包(http.Request)
- 至此,请求顺利到达 Gin 的 Engine(后续的处理看后续文章)
- 调用 Go 的 http.Server(什么时候有空再更深入的看看),设置 Handler 为 engine