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
}
}
}