Go猜想录
大道至简,悟者天成
gin-gonic/gin 源码分析

Gin

知名 web 框架一览
https://github.com/mingrammer/go-web-framework-stars

案例

package main

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

func main() {
	g := gin.Default()
	// 用户主动用的 MVC 模式
	ctl := &UserController{}
	g.GET("/user", ctl.GetUser)
	g.Run(":8080")
}

type UserController struct {
}

func (c *UserController) GetUser(ctx *gin.Context) {
	ctx.String(200, "hello, world")
}

IRoutes 接口

核心接口 IRoutes:提供的是注册路由的抽象。

Gin 和 Beego 的区别是没有 Controller 的抽象,也就没有了默认的 MVC 模式,这种设计更为灵活。 中间件设计者不需要考虑这种问题,这样框架的使用者就可以按需选用合适的组织架构。

// IRoutes defines all router handle interface.
type IRoutes interface {
    // 提供了用户接入自定义逻辑的能力,这个一般情况下也被看做是插件机制。
	Use(...HandlerFunc) IRoutes

    // 9 种 http 方法
	Handle(string, string, ...HandlerFunc) IRoutes
	Any(string, ...HandlerFunc) IRoutes
	GET(string, ...HandlerFunc) IRoutes
	POST(string, ...HandlerFunc) IRoutes
	DELETE(string, ...HandlerFunc) IRoutes
	PATCH(string, ...HandlerFunc) IRoutes
	PUT(string, ...HandlerFunc) IRoutes
	OPTIONS(string, ...HandlerFunc) IRoutes
	HEAD(string, ...HandlerFunc) IRoutes

    // 静态文件的接口
	StaticFile(string, string) IRoutes
	StaticFileFS(string, string, http.FileSystem) IRoutes
	Static(string, string) IRoutes
	StaticFS(string, http.FileSystem) IRoutes
}

Engine 实现

type Engine struct {
    // 路由组
    RouterGroup
    // ...
    // context 对象池
    pool             sync.Pool
    // 方法路由树
    trees            methodTrees
    // ...
}
  • 实现了路由树功能(注册和匹配路由)
  • 它本身可以作为一个 Handler 传递到 http 包,用于启动服务器 (Run 方法就是实现这个功能)
// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
// It is a shortcut for http.ListenAndServe(addr, router)
// Note: this method will block the calling goroutine indefinitely unless an error happens.
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.Handler())
	return
}

Engine 的路由树功能本质上是依赖于 methodTree 的。

methodTrees 和 methodTree

methodTree 才是真实的路由树。

Gin 定义了 methodTrees,它实际上代表的是森林,即每一个 HTTP 方法都对应到一棵树。

type Engine struct {
    ...
    noRoute          HandlersChain
	noMethod         HandlersChain
	pool             sync.Pool
	// Engine 字段
	trees            methodTrees
	maxParams        uint16
	maxSections      uint16
	trustedProxies   []string
    ...
}
type methodTree struct {
	method string
	root   *node
}

type methodTrees []methodTree

HandlerFunc 和 HandlersChain

HandlerFunc 定义了核心抽象 —— 处理逻辑。

在默认情况下,它代表了注册路由的业务代码。

HandlersChain 则是构造了责任链模式。

// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)

// HandlersChain defines a HandlerFunc slice.
type HandlersChain []HandlerFunc

Untitled 2.png

最后一个才是封装了业务逻辑的 HandlerFunc

Context 抽象

Context 也是代表了执行的上下文,提供了丰富的 APl:

  • 处理请求的 API,代表的是以 Get 和 Bind 为前缀的方法
    • 从不同的部位读取数据
      • AddParam(key, value string)
      • GetStringMapStringSlice(key string) (smss map[string][]string)
      • Param(key string) string
      • Query(key string) (value string)
      • DefaultQuery(key, defaultValue string) string
      • GetQuery(key string) (string, bool)
    • Bind 和 ShouldBind 类方法都是将输入转化为一个具体的结构体
      • Bind(obj any) error
      • ShouldBind(obj any) error
      • MustBindWith(obj any, b binding.Binding) error
    • 从 Keys 里面读取数据
      • Get(key string) (value any, exists bool)
      • MustGet(key string) any
  • 处理响应的 API,例如返回 JSON 或者 XML 响应的方法
    • Gin 的 Context 控制着 Handler 的调度,所以还包含中断后续 Handler 执行的方法
      • Abort()
      • AbortWithError(code int, err error) *Error
      • AbortWithStatus(code int)
      • AbortWithStatusJSON(code int, jsonObj any)
    • 返回具体格式的响应
      • AsciiJSON(code int, obj any)
      • SecureJSON(code int, obj any)
      • JSON(code int, obj any)
      • JSONP(code int, obj any)
      • PureJSON(code int, obj any)
      • XML(code int, obj any)
      • YAML(code int, obj any)
      • TOML(code int, obj any)
      • ProtoBuf(code int, obj any)
  • 渲染页面,如 HTML 方法
// 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.
type Context struct {
	writermem responseWriter
	Request   *http.Request
	Writer    ResponseWriter

	Params   Params
	handlers HandlersChain
	index    int8
	fullPath string

	engine       *Engine
	params       *Params
	skippedNodes *[]skippedNode

	// This mutex protects Keys map.
	mu sync.RWMutex

	// Keys is a key/value pair exclusively for the context of each request.
	Keys map[string]any

	// Errors is a list of errors attached to all the handlers/middlewares who used this context.
	Errors errorMsgs

	// Accepted defines a list of manually accepted formats for content negotiation.
	Accepted []string

	// queryCache caches the query result from c.Request.URL.Query().
	queryCache url.Values

	// formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,
	// or PUT body parameters.
	formCache url.Values

	// SameSite allows a server to define a cookie attribute making it impossible for
	// the browser to send this cookie along with cross-site requests.
	sameSite http.SameSite
}

Untitled 3.png

路由树实现

Gin 的关键结构体更加直观:

  • methodTrees:也就是路由树也是按照 HTTP 方法组织的,例如 GET 会有一棵路由树
  • methodTree:定义了单棵树。树在 Gin 里面采用的是 children 的定义方式,即树由节点构成
  • node:代表树上的一个节点,里面维持住了 children,即子节点。同时有 nodeType 和 wildChild 来标记一些特殊节点
type Engine struct {
    ...
	trees            methodTrees
    ...
}

type methodTrees []methodTree

type methodTree struct {
	method string
	root   *node
}

type node struct {
	path      string
	indices   string
	// 通配符
	wildChild bool
	nType     nodeType
	priority  uint32
	children  []*node // child nodes, at most 1 :param style node at the end of the array
	handlers  HandlersChain
	fullPath  string
}

Untitled 1.png

Gin 是利用路由的公共前缀来构造路由树。

AOP 方案

Gin 用的是半集中式设计,由 Context 来调度。但也是实现者在 HandlerFunc 里面主动调用下一个。

type Context struct {
    ...
    handlers HandlersChain
    ...
}

// HandlersChain defines a HandlerFunc slice.
type HandlersChain []HandlerFunc

// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)


/************************************/
/*********** FLOW CONTROL ***********/
/************************************/

// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in GitHub.
func (c *Context) Next() {
	c.index++
	for c.index < int8(len(c.handlers)) {
		if c.handlers[c.index] == nil {
			continue
		}
		c.handlers[c.index](c)
		c.index++
	}
}

Session 设计

https://github.com/gin-contrib/sessions

是通过嵌入一个 Middleware 来嵌入到请求处理的过程中。

Gin 中 Session 也可以看做是对 gorilla Session 的封装。

gorilla 采用了一种刷新的设计策略。即数据最开始存放在内存,也就是 Values 里面,后面再调用 Save 来真的刷新到存储里面,比如说刷新到 Redis 里面。

抽象总结

  • methodTree & node
    • HandlerFunc
    • Context
    • Engine

延伸阅读


知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。