Gin源码阅读 从0.1开始

最近打算开始学习一下Gin的源代码,现在Gin已经是一个十分成熟的框架了,代码量也不少,阅读起来还是有一定的难度,所以我打算从0.1版本开始阅读学习,一直到最新的一个版本。跟随着Gin的源码一步一步的学习成长。

目录结构

Gin 0.1的代码量十分的少, 主要代码一共也只有五个文件,代码中的注释也比较详细

1
2
3
4
5
6
│  auth.go
│ gin.go
│ logger.go
│ README.md
│ recovery.go
│ validation.go

跑起来

Gin 0.1的代码量十分的少,但是还是先从readme的示例开始说起

首先下面这一段代码是直接跑不起来的,不知道是代码本身的bug还是因为Go语言的版本变化导致的,首先我们需要修改几个地方

1
2
3
4
5
6
7
8
9
10
11
12
import "github.com/gin-gonic/gin"

func main() {
r := gin.Default()
r.GET("ping", func(c *gin.Context){
c.String("pong")
})

// Listen and server on 0.0.0.0:8080
r.Run(":80")

}

第一次修改

main.go

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

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

func main() {
r := gin.Default()
// 在这儿必须在ping,前加上/,不然会导致panic
r.GET("/ping", func(c *gin.Context){
// String 方法接受两个参数,但是实例只写了一个
c.String(http.StatusOK,"pong")
})

// Listen and server on 0.0.0.0:8080
r.Run(":80")
}

gin.go

1
2
3
4
5
6
7
8
9
10
11
// Returns a new blank Engine instance without any middleware attached.
// The most basic configuration
func New() *Engine {
engine := &Engine{}
engine.RouterGroup = &RouterGroup{nil, "", nil, engine}
engine.router = httprouter.New()
// NotFound 是一个http.Handler的接口,但是源码当中赋值了一个方法给他
engine.router.NotFound = engine
// engine.router.NotFound = engine.handle404
return engine
}

好了修改完成之后就可以运行, go run main.go成功运行了,但是还有一个bug,只能访问一次,就会因为stack overflow退出

查看一下gin.go, ServeHTTP可以发现,gin是直接调用了httprouterServeHTTP方法

1
2
3
4
// ServeHTTP makes the router implement the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
engine.router.ServeHTTP(w, req)
}

继续追踪,可以发现在httprouterServeHTTP方法最后有一段判定404的代码,这时候就可以发现这是之前修改gin.go` engine.router.NotFound = engine这段代码造成的,由于Chrome浏览器访问的时候会尝试访问/favicon.ico这个文件,然而我们在路由当中并没有定义,此时就是404,这时候由于之前我们在初始化的时候,给router传递的NotFoundengine,而engine.ServeHTTP调用了router.ServeHTTP`这时候就造成了无限递归,导致最后退出

1
2
3
4
5
6
// Handle 404
if r.NotFound != nil {
r.NotFound.ServeHTTP(w, req)
} else {
http.NotFound(w, req)
}

第二次修改

gin.go

1
2
3
4
5
6
7
8
9
10
11
// Returns a new blank Engine instance without any middleware attached.
// The most basic configuration
func New() *Engine {
engine := &Engine{}
engine.RouterGroup = &RouterGroup{nil, "", nil, engine}
engine.router = httprouter.New()
// NotFound 是一个http.Handler的接口,但是源码当中赋值了一个方法给他
// 注释掉即可
// engine.router.NotFound = engine.handle404
return engine
}

go run main.go成功运行,就没有问题了

Gin源码分析

跑起来之后就具体看看源码,初始版本的Gin当中拥有三个比较重要的struct,也是核心的组成部分

Context

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Context是gin当中最为重要的一部分
// 它用于在中间件当中传递变量,管理流程。例如接受json请求,并返回json
// Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example.
Context struct {
// ServeHTTP 第二个参数,请求体
Req *http.Request
// ServeHTTP 第一次参数,响应
Writer http.ResponseWriter
// 可以设置的值
Keys map[string]interface{}
// 错误信息
Errors []ErrorMsg
// 请求参数
Params httprouter.Params
// = 中间件 + 请求处理函数(最后一个)
handlers []HandlerFunc
// Engine 实例
engine *Engine
// 当前处理到的Handler下标
index int8
}

// 下一个中间件
Next()
// 终止处理,直接返回
Abort(code int)
// 添加错误信息,并且终止处理
Fail(code int, err error)
// 添加错误信息
Error(err error, meta interface{})

// 给Context.Keys添加值
Set(key string, item interface{})
// 获取Context.Keys的值,如果不存在会导致panic
Get(key string) interface{}

// 将请求体的参数作为json解析
ParseBody(item interface{}) error
// 同ParseBody,但是如果不是一个可解析的json会调用Fail(400)终止请求
EnsureBody(item interface{}) bool

// 下面是和返回相关的函数,code 参数均表示http status

// 返回json
JSON(code int, obj interface{})
// 返回xml
XML(code int, obj interface{})
// HTML模板渲染,使用golang标准库的模板库
HTML(code int, name string, data interface{})
// 返回字符串
String(code int, msg string)
// 返回流数据
Data(code int, data []byte)

RouterGroup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// RouterGroup用于管理路由,一个RouterGroup和一个前缀以及一组中间件关联
// Used internally to configure router, a RouterGroup is associated with a prefix
// and an array of handlers (middlewares)
RouterGroup struct {
// 中间件
Handlers []HandlerFunc
// 路径前缀
prefix string
parent *RouterGroup
// Engine 实例
engine *Engine
}

// 新建一个Context,用于传递这个路由组的数据
createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context
// 添加一个中间件到这个路由组
Use(middlewares ...HandlerFunc)
// 新建一个路由组
Group(component string, handlers ...HandlerFunc) *RouterGroup
// 注册一个路由
Handle(method, p string, handlers []HandlerFunc)
// 调用Handle方法注册一个POST路由
POST(path string, handlers ...HandlerFunc)
// 调用Handle方法注册一个GET路由
GET(path string, handlers ...HandlerFunc)
// 调用Handle方法注册一个DELETE路由
DELETE(path string, handlers ...HandlerFunc)
// 调用Handle方法注册一个PATCH路由
PATCH(path string, handlers ...HandlerFunc)
// 调用Handle方法注册一个PUT路由
PUT(path string, handlers ...HandlerFunc)
// 组合中间件,将传入的Handlers放在已有的Handlers后面
combineHandlers(handlers []HandlerFunc) []HandlerFunc

Engine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 用于表示Web框架,包含了fast HTTProuter和一个全局的中间件列表
// Represents the web framework, it wrappers the blazing fast httprouter multiplexer and a list of global middlewares.
Engine struct {
// 路由组
*RouterGroup
// 404 处理
handlers404 []HandlerFunc
// http router 实例
router *httprouter.Router
// 模板
HTMLTemplates *template.Template
}
// 加载HTML模板
LoadHTMLTemplates(pattern string)
// 设置404方法
NotFound404(handlers ...HandlerFunc)
// 默认的404方法,但是这个版本并没有使用上
handle404(w http.ResponseWriter, req *http.Request)
// 保存文件
ServeFiles(path string, root http.FileSystem)
// 实现http.Handler接口
ServeHTTP(w http.ResponseWriter, req *http.Request)
// 调用http.ListenAndServe启动服务器
Run(addr string)

0.1版本的gin只是一个极小,极为简单的工具箱,主要提供了一个简单的路由和简单的中间件实现,搞清楚下面这两个问题这个框架的也就明白了。

  1. 一个使用了Gin的Web应用,从初始化到启动的流程?

  2. 一个请求从接收到返回经历了什么?

应用流程

1.首先创建了一个engine实例,注册了两个两个基本的中间件

1
gin.Default() -> gin.New() -> engine.Use(Recovery(), Logger())

2.然后使用group.Handle方法注册路由, 关键代码如下,将路由添加到http router的树中,当执行handler方法的时候,会创建一个Context并且从头开始执行

1
2
3
4
group.engine.router.Handle(method, p, 
func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
group.createContext(w, req, params, handlers).Next()
})

3.调用http.ListenAndServe监听指定端口,启动服务器

请求流程

  1. 当服务器收到请求时,会调用之前注册的engine.ServeHTTP方法,查找路由
  2. engine.ServeHTTP方法使用了httprouterServeHTTP方法,这是会通过请求的path从已注册的路由树上获取对应的路由,并且执行其handler方法,如上所示,handler方法内部通过创建一个router group对应的Context从头开始执行所有的中间件以及注册路由时注册的请求处理函数
  3. 从请求处理函数中返回信息