Gin-gonic框架源码阅读

  15 mins to read  

List [CTL]

    最初在想究竟看看哪个框架,最后觉得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,以此来连接组合