今天是我golang框架閱讀系列第三篇文章,今天咱們主要看看gin的框架執行流程。關於golang框架生命週期源碼閱讀下面是個人計劃:node
計劃 | 狀態 |
---|---|
Go框架解析-beego | done |
Go框架解析-iris | done |
Go框架解析-gin | done |
Go框架解析-echo | doing |
Go框架解析-revel | doing |
Go框架解析-Martini | doing |
再完成各個golang框架生命週期的解析以後,我會計劃對這幾個框架的優略進行一個系列分析,因爲業內大多都是性能分析的比較多,我可能會更側重於如下維度:git
第一波咱們主要把重點放在框架設計上面。github
上次閱讀iris咱們使用的glide安裝的,今天咱們安裝gin嘗試下使用gomod,具體步驟以下。golang
使用go mod安裝:算法
// 初始化go.mod文件
go mod init gin-code-read
// 安裝gin
go get github.com/gin-gonic/gin
// 複製依賴到vendor目錄
go mod vendor
複製代碼
啓動一個簡單的gin http服務:bash
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()
}
複製代碼
看上面的啓動代碼是否是很熟悉,和iris很像是吧,一樣的Default
方法。網絡
看完gin框架流程我有大體以下幾個感觸:閉包
總之,目前就一個感覺:app
Gin是我認爲的一個GO框架應該有的樣子框架
下圖就是我對整個Gin框架生命週期的輸出,因爲圖片過大存在平臺壓縮的可能,建議你們直接查看原圖連接。
訪問圖片源地址查看大圖 user-gold-cdn.xitu.io/2019/7/7/16…
原圖查看連接: user-gold-cdn.xitu.io/2019/7/7/16…
// 獲取一個gin框架實例
gin.Default()
⬇️
// 具體的Default方法
func Default() *Engine {
// 調試模式日誌輸出
// 🌟很不錯的設計
debugPrintWARNINGDefault()
// 建立一個gin框架實例
engine := New()
// 是否是很眼熟 和iris裏註冊中間件的方式一致
// 不過比iris好的是支持多參數 iris則是得調用屢次
engine.Use(Logger(), Recovery())
return engine
}
⬇️
// 建立一個gin框架實例 具體方法
func New() *Engine {
// 調試模式日誌輸出
debugPrintWARNINGNew()
// 先插入一個小話題,可能好多人都在想爲何叫gin呢?
// 哈哈,這個框架實例的結構體實際命名的Engine, 很明顯gin就是一個很個性的簡稱了,是否是真相大白了。
// 初始化一個Engine實例
engine := &Engine{
// 路由組
// 給框架實例綁定上一個路由組
RouterGroup: RouterGroup{
// engine.Use 註冊的中間方法到這裏
Handlers: nil,
basePath: "/",
// 是不是路由根節點
root: true,
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
AppEngine: defaultAppEngine,
UseRawPath: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
// 路由樹
// 咱們的路由最終註冊到了這裏
trees: make(methodTrees, 0, 9),
delims: render.Delims{Left: "{{", Right: "}}"},
secureJsonPrefix: "while(1);",
}
// RouterGroup綁定engine自身的實例
// 不太明白爲什麼如此設計
// 職責分明麼?
engine.RouterGroup.engine = engine
// 綁定從實例池獲取上下文的閉包方法
engine.pool.New = func() interface{} {
// 獲取一個Context實例
return engine.allocateContext()
}
// 返回框架實例
return engine
}
⬇️
// 註冊日誌&goroutin panic捕獲中間件
engine.Use(Logger(), Recovery())
⬇️
// 具體的註冊中間件的方法
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
// 上面 是一個engine框架實例初始化的關鍵代碼
// 咱們基本看完了
// --------------router--------------
// 接下來 開始看路由註冊部分
// 註冊GET請求路由
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
// 往路由組內 註冊GET請求路由
return group.handle("GET", relativePath, handlers)
}
⬇️
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath)
// 把中間件的handle和該路由的handle合併
handlers = group.combineHandlers(handlers)
// 註冊一個GET集合的路由
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
⬇️
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
assert1(path[0] == '/', "path must begin with '/'")
assert1(method != "", "HTTP method can not be empty")
assert1(len(handlers) > 0, "there must be at least one handler")
debugPrintRoute(method, path, handlers)
// 檢查有沒有對應method集合的路由
root := engine.trees.get(method)
if root == nil {
// 沒有 建立一個新的路由節點
root = new(node)
// 添加該method的路由tree到當前的路由到路由樹裏
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
// 添加路由
root.addRoute(path, handlers)
}
⬇️
// 很關鍵
// 路由樹節點
type node struct {
// 路由path
path string
indices string
// 子路由節點
children []*node
// 全部的handle 構成一個鏈
handlers HandlersChain
priority uint32
nType nodeType
maxParams uint8
wildChild bool
}
// 上面
// 咱們基本看完了
// --------------http server--------------
// 接下來 開始看gin如何啓動的http server
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
// 執行http包的ListenAndServe方法 啓動路由
// engine實現了http.Handler接口 因此在這裏做爲參數傳參進去
// 後面咱們再看engine.ServeHTTP的具體邏輯
err = http.ListenAndServe(address, engine)
return
}
⬇️
// engine自身就實現了Handler接口
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
⬇️
// 下面就是網絡相關了
// 監聽IP+端口
ln, err := net.Listen("tcp", addr)
⬇️
// 上面執行完了監聽
// 接着就是Serve
srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
⬇️
// Accept請求
rw, e := l.Accept()
⬇️
// 使用goroutine去處理一個請求
// 最終就執行的是engine的ServeHTTP方法
go c.serve(ctx)
// 上面服務已經啓動起來了
// --------------handle request--------------
// 接着咱們來看看engine的ServeHTTP方法的具體內容
// engine實現http.Handler接口ServeHTTP的具體方法
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 獲取一個上下文實例
// 從實例池獲取 性能高
c := engine.pool.Get().(*Context)
// 重置獲取到的上下文實例的http.ResponseWriter
c.writermem.reset(w)
// 重置獲取到的上下文實例*http.Request
c.Request = req
// 重置獲取到的上下文實例的其餘屬性
c.reset()
// 實際處理請求的地方
// 傳遞當前的上下文
engine.handleHTTPRequest(c)
//歸還上下文實例
engine.pool.Put(c)
}
⬇️
// 具體執行路由的方法
engine.handleHTTPRequest(c)
⬇️
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
// 這裏尋找當前請求method的路由樹節點
// 我在想這裏爲啥不用map呢?
// 雖然說也遍歷不了幾回
if t[i].method != httpMethod {
continue
}
// 找到節點
root := t[i].root
// 很關鍵的地方
// 尋找當前請求的路由
handlers, params, tsr := root.getValue(path, c.Params, unescape)
if handlers != nil {
// 把找到的handles賦值給上下文
c.handlers = handlers
// 把找到的入參賦值給上下文
c.Params = params
// 執行handle
c.Next()
// 處理響應內容
c.writermem.WriteHeaderNow()
return
}
...
}
// 方法樹結構體
type methodTree struct {
// HTTP Method
method string
// 當前HTTP Method的路由節點
root *node
}
// 方法樹集合
type methodTrees []methodTree
⬇️
// 執行handle
func (c *Context) Next() {
// 上下文處理以後c.index被執爲-1
c.index++
for s := int8(len(c.handlers)); c.index < s; c.index++ {
// 遍歷執行全部handle(其實就是中間件+路由handle)
// 首先感受這裏的設計又是似曾相識 iris不是也是這樣麼 不懂了 哈哈
// 其次感受這裏設計的很通常 遍歷?多無聊,這裏多麼適合「責任鏈模式」
// 以後給你們帶來關於這個handle執行的「責任鏈模式」的設計
c.handlers[c.index](c)
}
}
// Context的重置方法
func (c *Context) reset() {
c.Writer = &c.writermem
c.Params = c.Params[0:0]
c.handlers = nil
// 很關鍵 注意這裏是-1哦
c.index = -1
c.Keys = nil
c.Errors = c.Errors[0:0]
c.Accepted = nil
}
複製代碼
最後咱們再簡單的回顧下上面的流程,從上圖看來,是否是相對於iris簡單了好多。
《Golang框架解析》系列文章連接以下: