文章 gin源码初读
Post
Cancel

gin源码初读

Hello World

众所周知, 编程起源Hello World, 让俺们从Hello World开始吧.

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

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

func main() {
	r := gin.Default()  //  返回一个Engine实例, 它包含路由, 中间件和配置信息


	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // listen and serve on 0.0.0.0:8080
}

gin.Engine

gin.Default()返回了一个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
25
26
27
28
type Engine struct {
	// Engine组合了RouterGroup, RouterGroup在内部用于配置router, RouterGroup与一个前缀和一个处理程序数组(中间件)相关联。
	RouterGroup

	RedirectTrailingSlash bool
	RedirectFixedPath bool
	HandleMethodNotAllowed bool
	ForwardedByClientIP    bool

	AppEngine bool
	UseRawPath bool

	UnescapePathValues bool
	MaxMultipartMemory int64
  RemoveExtraSlash bool

	delims           render.Delims
	secureJSONPrefix string
	HTMLRender       render.HTMLRender
	FuncMap          template.FuncMap
	allNoRoute       HandlersChain
	allNoMethod      HandlersChain
	noRoute          HandlersChain
	noMethod         HandlersChain
	pool             sync.Pool
	trees            methodTrees
	maxParams        uint16
}

这里比较重要的就是RouterGroup, 路由注册,分组和嵌套的方法全部来自于他

1
2
3
4
5
6
type RouterGroup struct {
	Handlers HandlersChain
	basePath string  // 用于路由分组的时候
	engine   *Engine
	root     bool
}

Default所做的事情就是调用New返回一个不带中间件的Engine并且添加两个中间件. New则返回一个Engine对象, 再把自己放到RouterGroup

func New() *Engine {
	debugPrintWARNINGNew()
	engine := &Engine{
		RouterGroup: RouterGroup{
			Handlers: nil,
			basePath: "/",
			root:     true,
		},
    ...
	}
	engine.RouterGroup.engine = engine
	engine.pool.New = func() interface{} {
		return engine.allocateContext()
	}
	return engine
}

到现在Default的工作就完成惹

路由注册

路由注册的方法最后都会执行

1
2
3
4
5
6
7
// 所有的路由注册都会执行这个方法
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
	absolutePath := group.calculateAbsolutePath(relativePath)  // 获取完整路由
	handlers = group.combineHandlers(handlers) // 把RouterGroup的handler和函数的handler结合起来(中间件)
	group.engine.addRoute(httpMethod, absolutePath, handlers) // 注册
	return group.returnObj()
}

下面主要研究engine.addRoute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
	...
	root := engine.trees.get(method)  // 从路由树切片中获取该方法的树的根节点, 返回一个node
	if root == nil { // 如果没有根节点的话, 就新建一个根节点
		root = new(node)
		root.fullPath = "/"
		engine.trees = append(engine.trees, methodTree{method: method, root: root})
	}
	root.addRoute(path, handlers) // 组装路由树, 这个就不多写了

	// Update maxParams
	if paramsCount := countParams(path); paramsCount > engine.maxParams {
		engine.maxParams = paramsCount
	}
}

这里还有node这个类

1
2
3
4
5
6
7
8
9
10
type node struct {
	path      string
	indices   string
	wildChild bool
	nType     nodeType
	priority  uint32
	children  []*node  // 子节点
	handlers  HandlersChain  // 注册的方法
	fullPath  string
}

开始执行

这里执行使用的是Run方法, Run方法调用了http.ListenAndServe, 并将engine作为handler传了进去

1
2
3
4
5
6
7
8
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)  // ListenAndServe接受一个Handler
   return
}

请求来临时, 就会执行EngineServeHTTP方法, EngineServerHTTP方法如下

1
2
3
4
5
6
7
8
9
10
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	c := engine.pool.Get().(*Context)  // engine.pool 优化GC
	c.writermem.reset(w)
	c.Request = req
	c.reset()

	engine.handleHTTPRequest(c) // 处理Context

	engine.pool.Put(c)
}

handleHTTPRequest方法

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
func (engine *Engine) handleHTTPRequest(c *Context) {
	...
	for i, tl := 0, len(t); i < tl; i++ {
		if t[i].method != httpMethod {
			continue
		}
		root := t[i].root
		// Find route in tree
		value := root.getValue(rPath, c.params, unescape)
		if value.params != nil {
			c.Params = *value.params
		}
    // 开始执行handlers
		if value.handlers != nil {
			c.handlers = value.handlers
			c.fullPath = value.fullPath
			c.Next()
			c.writermem.WriteHeaderNow()
			return
		}
		... 
		break
	}

	...
	c.handlers = engine.allNoRoute
	serveError(c, http.StatusNotFound, default404Body)
}

上面的Next是不是很熟悉, 我们在写中间件的时候经常会用到这个方法

1
2
3
4
5
6
7
func (c *Context) Next() {
	c.index++ // c.index记录当前执行到哪个中间件
	for c.index < int8(len(c.handlers)) {
		c.handlers[c.index](c)
		c.index++
	}
}

reset会把Context.index设置成-1, 所以每次进入循环会取handlers中的第0个开始执行, 遇到Next就回去执行下一个. 这样就形成了一个函数栈.

http.ListenAndServe

这个是属于http包中的内容, 这里也说一下好了, ListenAndServe接受一个地址和一个handler, 之后便启动一个server

1
2
3
4
func ListenAndServe(addr string, handler Handler) error {
   server := &Server{Addr: addr, Handler: handler}
   return server.ListenAndServe()
}

接下来便是这两个方法

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
func (srv *Server) ListenAndServe() error {
	if srv.shuttingDown() {
		return ErrServerClosed
	}
	addr := srv.Addr
	if addr == "" {
		addr = ":http"
	}
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

func (srv *Server) Serve(l net.Listener) error {
	...
	for {
    // 这里不断的监听请求
		rw, e := l.Accept()
		...
		tempDelay = 0
		c := srv.newConn(rw)
		c.setState(c.rwc, StateNew) // before Serve can return
    // 并且启用协程去处理
		go c.serve(ctx)
	}
}

func (srv *Server) newConn(rwc net.Conn) *conn {
	c := &conn{
		server: srv,
		rwc:    rwc,
	}
	if debugServerConnections {
		c.rwc = newLoggingConn("server", c.rwc)
	}
	return c
}

结构体conn

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
type conn struct {
   server *Server
   rwc net.Conn
}

func (c *conn) serve(ctx context.Context) {
  ...
	for {
    ...
    // 然后看着一句就好了, c.server就是Server对象
		serverHandler{c.server}.ServeHTTP(w, w.req)
    ...
}
  
type serverHandler struct {
	srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
	handler := sh.srv.Handler // handler就是Server对象的Handler, 也就是ListenAndServe时传进来的Handler
	if handler == nil {
		handler = DefaultServeMux
	}
	if req.RequestURI == "*" && req.Method == "OPTIONS" {
		handler = globalOptionsHandler{}
	}
	handler.ServeHTTP(rw, req) // 最后执行的就是传进来的Handler的ServeHTTP方法
}

总结

强类型的源代码还是比较容易阅读的, 不会突然的多出来一些方法和属性. 这里把gin的基础结构说了一下, 至于路由树的构造没有详细的去看, 大概就是每个方法对应着一棵树, 最短公共路由下包含着子路由. goto语句不是太好理解.

This post is licensed under CC BY 4.0 by the author.
Contents