天天看點

Gin源碼分析 - 程式運作流程簡介

1 最小程式

首先通過對一個最小程式的分析,說明一下Gin程式的基本運作流程以及核心的元件。下面這個程式啟動過後傳回一個JSON字元串,{"message" : "pong"}。

package main

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

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run()
}           

下面我将深入代碼,将整個程式的運作邏輯呈現在你的面前,由于本節僅僅是一個基礎的介紹,是以主要部分結構和方法會進行删減,進保留主幹。

2 建立引擎

r := gin.Default()           

第1行語句建立一個gin運作引擎

func Default() *Engine {
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine
}           

該函數建立一個預設的Engine對象,Engine對象故名思義是 gin 的運作引擎,關鍵的字段如下所示:

type Engine struct {
	RouterGroup
	pool             sync.Pool
	trees            methodTrees
}

func New() *Engine {
	engine := &Engine{
    // 初始化RouterGroup
		RouterGroup: RouterGroup{ ... },
    // 建立方法輸,由于有9種方法
		trees:       make(methodTrees, 0, 9),
	}
  // 建立Context對象池
	engine.pool.New = func() interface{} {
		return engine.allocateContext()
	}
	return engine
}           
  • RouterGroup:管理中間件,engine.User(Logger(), Recovery()) 就是将日志和恢複中間件添加到 RouterGroup中進行管理。
  • pool:在進行HTTP請求處理的過程中需要采用上下文Context進行管理,由于需要頻繁的建立和銷毀Context是以采用sync.Pool提高效率。
  • tree:每一個 HTTP 方法會有一顆方法樹,方法樹記錄了路徑和路徑上的處理函數。
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
  // 将中間件添加到RouterGroup中
	engine.RouterGroup.Use(middleware...)
	return engine
}

// 中間件,實際上是一個參數為Context的函數
type HandlerFunc func(*Context)

// 管理中間件的是一個切片
type HandlersChain []HandlerFunc

type RouterGroup struct {
	Handlers HandlersChain
}

func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}           

上面的方面說明了注冊中間件的過程,RouterGroup的核心字段是一個HandlerFunc的切片,所有的中間件按照順序儲存在這個切片中。

3 注冊處理方法

r.GET("/ping", func(c *gin.Context) {
     c.JSON(200, gin.H{
          "message": "pong",
     })
})           

第2行語句,注冊一個URL為/ping的處理方法,該方法傳回一個JSON。

// GET方法
const (
	MethodGet = "GET"
)

// 調用handle完成注冊
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodGet, relativePath, handlers)
}

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
	// 擷取完成的路徑
  absolutePath := group.calculateAbsolutePath(relativePath)
  // 将中間件方法和本URL處理方法組合形成一個方法鍊,然後注冊到tree中
	handlers = group.combineHandlers(handlers)
  // 整個方法注冊的核心函數,完成方法的注冊
	group.engine.addRoute(httpMethod, absolutePath, handlers)
	return group.returnObj()
}
           

engine.addRoute是方法注冊的入口函數,簡單來說就是将這個URL的方法鍊注冊到相應的方法樹中,為了提高URL的檢索效率,這個方法樹采用了Radix Tree的資料結構,在本小節中可以忽略,總值一句話通過這個方法樹可以高效的檢索的每個URL的對應處理方法鍊。

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
  // 獲得這個方法的方法(GET方法)資料
	root := engine.trees.get(method)
  // 将該URL的處理方法鍊添加到方法樹中
	root.addRoute(path, handlers)
}

// addRoute adds a node with the given handle to the path.
// Not concurrency-safe!
func (n *node) addRoute(path string, handlers HandlersChain) {
	fullPath := path
        ...
        // 建立相應的葉子結點或者找到相應的葉子節點
        n = child
        ...
        n.handlers = handlers
	n.fullPath = fullPath
	return
}

type node struct {
	handlers  HandlersChain
	fullPath  string
}           

可以看到每個每個方法樹中的節點包括 handlers和fullPath兩個字段,分别儲存相應的URL路徑和處理方法鍊。

4 系統運作

r.Run()           

第3行語句,調用Go的HTTP包,啟動WEB服務。

func (engine *Engine) Run(addr ...string) (err error) {
  // 擷取位址
	address := resolveAddress(addr)
  // http監聽并且服務
	err = http.ListenAndServe(address, engine)
	return
}

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

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}           

Engine實作了ServeHTTP方法,該方法是HTTP的回調接口,當HTTP子產品接收到一個HTTP請求後将調用Engine的ServeHTTP方法。

5 處理HTTP請求

簡單來說就是從方法樹中找到相應的方法進行處理,但是由于存在中間件,是以需要在中間件中通過Context儲存上下文的資料,具體個過程在中間件一節在詳細說。

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  // 從對象池中擷取一個Context
	c := engine.pool.Get().(*Context)
  // 對Context進行必要的初始化
	c.writermem.reset(w)
	c.Request = req
	c.reset()
        
  // 處理該HTTP請求
	engine.handleHTTPRequest(c)

  // 完成對HTTP的請求後,釋放該Context
	engine.pool.Put(c)
}

func (engine *Engine) handleHTTPRequest(c *Context) {
	// 獲得HTTP請求方法類型
  httpMethod := c.Request.Method
  // 獲得URL路徑
	rPath := c.Request.URL.Path

	t := engine.trees
	for i, tl := 0, len(t); i < tl; i++ {
    // 獲得該HTTP請求的方法樹
		if t[i].method != httpMethod {
			continue
		}

		root := t[i].root
		// 找到該URL的處理方法鍊
		value := root.getValue(rPath, c.params, c.skippedNodes, unescape)

		if value.handlers != nil {
      // 通過上下文的Next方法實作中間件的調用以及方法的調用
			c.handlers = value.handlers
			c.fullPath = value.fullPath
			c.Next()
			c.writermem.WriteHeaderNow()
			return
		}
	}
}           

繼續閱讀