Gin源码阅读 从0.1开始

2,576 阅读6分钟
原文链接: lailin.xyz

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

目录结构

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

│  auth.go
│  gin.go
│  logger.go
│  README.md
│  recovery.go
│  validation.go

跑起来

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

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

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

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

// 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方法

// 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.goengine.router.NotFound = engine这段代码造成的,由于Chrome浏览器访问的时候会尝试访问/favicon.ico这个文件,然而我们在路由当中并没有定义,此时就是404,这时候由于之前我们在初始化的时候,给router传递的NotFoundengine,而engine.ServeHTTP调用了router.ServeHTTP这时候就造成了无限递归,导致最后退出

// Handle 404
if r.NotFound != nil {
	r.NotFound.ServeHTTP(w, req)
} else {
	http.NotFound(w, req)
}

第二次修改

gin.go

// 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

// 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

// 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

// 用于表示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实例,注册了两个两个基本的中间件

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

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

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. 从请求处理函数中返回信息

下一篇,看看0.2与0.1之间的演进