Gin-gonic框架源码阅读

  15 mins to read  

最初在想究竟看看哪个框架,最后觉得gin[杜松子酒]挺不错的,是个轻量级框架,API很简洁,官方设计的logo也不错

学习的是0.1 release的,只有五个文件很简洁(最新版几万行代码杀了我吧)


Gin

0.1版本只有五个文件:

gin/
   |__auth.go
   |__gin.go
   |__logger.go
   |__recovery.go
   |__validation.go

auth为http认证模块,我暂时还没用过。logger和recovery为两个默认中间件模块,所以重点在gin.go

gin.Context,ctx是整合了http.ResponseWriterhttp.Request的上下文对象,成员有:

type Context struct {
    Req      *http.Request
    Writer   http.ResponseWriter
    Keys     map[string]interface{}
    Errors   []ErrorMsg
    Params   httprouter.Params
    handlers []HandlerFunc
    engine   *Engine
    index    int8
}

func (c *Context) Next() {
	c.index++
	s := int8(len(c.handlers))
	for ; c.index < s; c.index++ {
		c.handlers[c.index](c)
	}
}

func (c *Context) Abort(code int) {
	c.Writer.WriteHeader(code)
	c.index = AbortIndex
}

func (c *Context) Fail(code int, err error) {
	c.Error(err, "Operation aborted")
	c.Abort(code)
}

func (c *Context) Error(err error, meta interface{}) {
	c.Errors = append(c.Errors, ErrorMsg{
		Message: err.Error(),
		Meta:    meta,
	})
}

func (c *Context) Set(key string, item interface{}) {
	if c.Keys == nil {
		c.Keys = make(map[string]interface{})
	}
	c.Keys[key] = item
}
func (c *Context) Get(key string) interface{} {
	var ok bool
	var item interface{}
	if c.Keys != nil {
		item, ok = c.Keys[key]
	} else {
		item, ok = nil, false
	}
	if !ok || item == nil {
		log.Panicf("Key %s doesn't exist", key)
	}
	return item
}

func (c *Context) EnsureBody(item interface{}) bool {
	if err := c.ParseBody(item); err != nil {
		c.Fail(400, err)
		return false
	}
	return true

func (c *Context) ParseBody(item interface{}) error {
	decoder := json.NewDecoder(c.Req.Body)
	if err := decoder.Decode(&item); err == nil {
		return Val idate(c, item)
	} else {
		return err
	}
}

func (c *Context) JSON(code int, obj interface{}) {
	if code >= 0 {
		c.Writer.WriteHeader(code)
	}
	c.Writer.Header().Set("Content-Type", "application/json")
	encoder := json.NewEncoder(c.Writer)
	if err := encoder.Encode(obj); err != nil {
		c.Error(err, obj)
		http.Error(c.Writer, err.Error(), 500)
	}
}

func (c *Context) XML(code int, obj interface{}) {
	if code >= 0 {
		c.Writer.WriteHeader(code)
	}
	c.Writer.Header().Set("Content-Type", "application/xml")
	encoder := xml.NewEncoder(c.Writer)
	if err := encoder.Encode(obj); err != nil {
		c.Error(err, obj)
		http.Error(c.Writer, err.Error(), 500)
	}
}

func (c *Context) HTML(code int, name string, data interface{}) {
	if code >= 0 {
		c.Writer.WriteHeader(code)
	}
	c.Writer.Header().Set("Content-Type", "text/html")
	if err := c.engine.HTMLTemplates.ExecuteTemplate(c.Writer, name, data); err != nil {
		c.Error(err, map[string]interface{}{
			"name": name,
			"data": data,
		})
		http.Error(c.Writer, err.Error(), 500)
	}
}

func (c *Context) String(code int, msg string) {
	c.Writer.Header().Set("Content-Type", "text/plain")
	c.Writer.WriteHeader(code)
	c.Writer.Write([]byte(msg))
}

func (c *Context) Data(code int, data []byte) {
	c.Writer.WriteHeader(code)
	c.Writer.Write(data)
}

Keys是中间件传递参数所用,Errors会在logger中间件处理,Handlers是中间件函数的切片,engine也就是gin的核心对象。这里的ctx.Next函数很重要,我们到后面在说

RouterGroup struct {
    Handlers []HandlerFunc
    prefix   string
    parent   *RouterGroup
    engine   *Engine
}

Engine struct {
    *RouterGroup
    handlers404   []HandlerFunc
    router        *httprouter.Router
    HTMLTemplates *template.Template
}

engine内嵌了RouterGroup对象(所以Engine也可以当做RouterGroup用),而RouterGroup中有Engine字段,也就是以这样的形式,实现了gin框架Demo里的一组前缀相同的路由定义

v1 := router.Group("/v1")
{
    v1.POST("/login", loginEndpoint)
    v1.POST("/submit", submitEndpoint)
    v1.POST("/read", readEndpoint)
}

源码中在这里的工厂函数返回了Group实例:

func (group *RouterGroup) Group(component string, handlers ...HandlerFunc) *RouterGroup {
	prefix := path.Join(group.prefix, component)
	return &RouterGroup{
		Handlers: group.combineHandlers(handlers),
		parent:   group,
		prefix:   prefix,
		engine:   group.engine,
	}
}

这里的方法接收者实际是Engine,因为内嵌对象的方法自动提升到被嵌入对象。所以New出来的Engine是所有想关对象字段里的parent,而Group实例化的RouterGroup共享同一个Engine,然后在Group后续定义的路由里join了这里的prefix

再看gin.New函数:

func New() *Engine {
	engine := &Engine{}
	engine.RouterGroup = &RouterGroup{nil, "", nil, engine}
	engine.router = httprouter.New()
	engine.router.NotFound = engine.handle404
	return engine
}

创建了Engine实例并返回,而Default函数也是调用了New函数,并默认连接了两个中间件

接下来看Engine的成员函数,也就是一些gin框架run and serve的功能函数:

func (engine *Engine) LoadHTMLTemplates(pattern string) {
	engine.HTMLTemplates = template.Must(template.ParseGlob(pattern))
}

func (engine *Engine) NotFound404(handlers ...HandlerFunc) {
	engine.handlers404 = handlers
}

func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) {
	handlers := engine.combineHandlers(engine.handlers404)
	c := engine.createContext(w, req, nil, handlers)
	if engine.handlers404 == nil {
		http.NotFound(c.Writer, c.Req)
	} else {
		c.Writer.WriteHeader(404)
	}

	c.Next()
}

func (engine *Engine) ServeFiles(path string, root http.FileSystem) {
	engine.router.ServeFiles(path, root)
}

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	engine.router.ServeHTTP(w, req)
}

func (engine *Engine) Run(addr string) {
	http.ListenAndServe(addr, engine)
}

最后的路由函数是handle请求并处理的功能,即使不使用前缀URL,也依旧是调用了RouterGroup的方法

// Handle registers a new request handle and middlewares with the given path and method.
// The last handler should be the real handler, the other ones should be middlewares that can and should be shared among different routes.
func (group *RouterGroup) Handle(method, p string, handlers []HandlerFunc) {
	p = path.Join(group.prefix, p)
	handlers = group.combineHandlers(handlers)
	group.engine.router.Handle(method, p, func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
		group.createContext(w, req, params, handlers).Next()
	})
}

// POST is a shortcut for router.Handle("POST", path, handle)
func (group *RouterGroup) POST(path string, handlers ...HandlerFunc) {
	group.Handle("POST", path, handlers)
}

// GET is a shortcut for router.Handle("GET", path, handle)
func (group *RouterGroup) GET(path string, handlers ...HandlerFunc) {
	group.Handle("GET", path, handlers)
}

// DELETE is a shortcut for router.Handle("DELETE", path, handle)
func (group *RouterGroup) DELETE(path string, handlers ...HandlerFunc) {
	group.Handle("DELETE", path, handlers)
}

// PATCH is a shortcut for router.Handle("PATCH", path, handle)
func (group *RouterGroup) PATCH(path string, handlers ...HandlerFunc) {
	group.Handle("PATCH", path, handlers)
}

// PUT is a shortcut for router.Handle("PUT", path, handle)
func (group *RouterGroup) PUT(path string, handlers ...HandlerFunc) {
	group.Handle("PUT", path, handlers)
}

func (group *RouterGroup) combineHandlers(handlers []HandlerFunc) []HandlerFunc {
	s := len(group.Handlers) + len(handlers)
	h := make([]HandlerFunc, 0, s)
	h = append(h, group.Handlers...)
	h = append(h, handlers...)
	return h
}

看到这里的逻辑,中间的几个http方法函数都只是给Handler封装了一层,重点逻辑在于定义变长的Handler函数时,会将路由映射join进前缀,并定义函数–URL映射,匿名函数里是实例化ctx调用Next方法,也就是前面提到的:

func (c *Context) Next() {
	c.index++
	s := int8(len(c.handlers))
	for ; c.index < s; c.index++ {
		c.handlers[c.index](c)
	}
}

这里Context.indexuint8类型,也就是说针对一个URL所能定义的handler func小于1 << 8。在httprouter处理请求时,会调用刚刚定义的匿名函数,来依次调用处理这个URL的handler,最后想要达到的目的就是,中间件会被链式依次调用,且请求与响应时为栈式逆序。结合源码的注释:

// Handle registers a new request handle and middlewares with the given path and method.
// The last handler should be the real handler, the other ones should be middlewares that can and should be shared among different routes.

// Next should be used only in the middlewares.
// It executes the pending handlers in the chain inside the calling handler.

假设我们的某个函数有三个中间件,一个请求处理函数一个请求到来时,router会自动调用一次c.Next(),接着开始调用第一个中间件,中间件函数里会调用c.Next(),然后在中间件函数栈里继续遍历[]handlers,由于handle func传入的是*Context,所以c.index会随之++,接着会调用第二个中间件,然后在第二个中间件的函数栈继续调用第三个中间件,接着第三个里调用c.Next()会调用请求处理函数,接着请求处理函数执行完成后,第三个中间件的c.Next()返回,接着执行c.Next()后面的部分,也就是响应处理,执行完成返回到第二个中间件继续处理响应…etc.。而假如我们想在中间件里传递变量,就需要用到我前面说的Context.Key以及它的property方法Get/Set

也就是说中间件函数以c.Next()为分割,之前为请求处理部分,调用顺序是middleware1 -> middleware2 -> middleware3,之后为响应处理部分,调用顺序与请求处理相反,简单的导图:


中间件的函数签名如下,其实中间件与请求处理函数签名系统,唯一区别是中间件里调用了c.Next()

type HandlerFunc func(*Context)

RouterGroup用来连接中间件,ctx工厂函数的方法

func (group *RouterGroup) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context {
	return &Context{
		Writer:   w,
		Req:      req,
		index:    -1,
		engine:   group.engine,
		Params:   params,
		handlers: handlers,
	}
}

// Adds middlewares to the group, see example code in github.
func (group *RouterGroup) Use(middlewares ...HandlerFunc) {
	group.Handlers = append(group.Handlers, middlewares...)
}

最后就是常用的gin.H的原型,是一个string->interface{}的哈希表,可以用它来方便的传递msg:

type H map[string]interface{}

结尾总结一下,gin框架的核心是engine,它内嵌了RouterGroup结构体(也就是OOP的继承),就是用它来连接中间件、处理请求…etc.,请求来临时httprouter库会回调定义的匿名函数来运行ctx.Next方法,接着就是一连串中间件与请求处理函数

每一个RouterGroup对象存在一个Engine子对象,子对象里定义了templateRouter,在连接中间件函数时是保存在RouterGroupHandlers里,所以对于不同层级的URL,可以定义适用性不同的中间件,比如只在一个组路由中连接一个中间件

对于同一个Web App中不同的路由,它们都会共享同一个Engine,也就是gin.New实例化的Engineprefix url == ""),然后由Engine定义单路由/组路由来定义不同的映射,router.Handle方法会在定义路由前joinrouterprefix url,以此来连接组合