文章首發於同名公衆號,歡迎關注~
由於gin
的安裝教程已經處處都有了,因此這裏省略如何安裝, 建議直接去github
官方地址的README
中瀏覽安裝步驟,順便了解gin
框架的功能。 https://github.com/gin-gonic/gin
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.Run() // 監聽並在 0.0.0.0:8080 上啓動服務 }
上面就是使用了 gin
框架的最簡單的 demo
,接下來經過這個 demo
去一步步閱讀分析 gin
是如何啓動的。git
這裏是服務器啓動的地方,進去看看這個函數有什麼奧祕:github
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) return }
除去第一行和第四行打印,是否是有點像 go
原生的 http
庫?來看看 http
庫的 web
服務簡單 demo
web
package main import ( "log" "net/http" ) func main() { http.HandleFunc("/", indexHandler) log.Fatal(http.ListenAndServe(":8080", nil)) }
能夠看到 gin
框架啓動的時候,也是基於 http.ListenAndServe(addr string, handler Handler)
這個方法的,與原生的 http
庫的區別就是,ListenAndServe(addr string, handler Handler)
這個方法的第二個參數,傳入的是 engine
,咱們繼續來看看 ListenAndServe
方法的具體代碼:服務器
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
server.ListenAndServe()
已是服務監聽並啓動的方法,因此關鍵點在 Handler
這裏:框架
// A Handler responds to an HTTP request. // ... type Handler interface { ServeHTTP(ResponseWriter, *Request) }
這是 http
包裏的源碼註釋,其實寫得很詳細了,註釋不少,我直接省略了,感興趣的能夠去閱讀一下,不出意外,這個 Handler
是一個接口,從 gin.engine
的傳入已經能夠看出來了吧?註釋的意思是這個接口是處理程序響應HTTP請求,能夠理解爲,實現了這個接口,就能夠接收全部的請求並進行處理。函數
所以 gin
框架的入口就是從這裏開始,gin engine
實現了 ServeHTTP
,而後接管全部的請求走 gin 框架的處理方式。細心的讀者應該能發現,原生的 http web
服務傳入了 nil
,事實上是 http
庫也有一個默認的請求處理器,感興趣的讀者能夠去仔細閱讀研究一下 go
官方團隊的請求處理器實現哦~學習
那咱們繼續來看下 gin
框架是怎麼實現這個 ServeHTTP
方法的:測試
// gin.go // ServeHTTP conforms to the http.Handler interface. func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { c := engine.pool.Get().(*Context) // 從池裏獲取資源處理請求 c.writermem.reset(w) // 重置資源的 ResponseWriter c.Request = req // 賦值請求給 context c.reset() engine.handleHTTPRequest(c) // 將資源信息給到 handler 進一步處理 engine.pool.Put(c) // 請求處理結束,將資源放回池子 }
能夠看到實現並不難,代碼的含義我已經寫上了註釋,這裏的重點是 Context
,但不是這一篇文章的重點,我放在這一系列的後面進行講解。url
咱們只須要知道,gin
也是實現了 Handler
接口,因此能夠將請求按 gin
的處理方式進行處理,也就是咱們能夠使用 gin
來作 web
服務框架的起點。spa
接下來進去 Default
函數去看具體實現,代碼以下:
// gin.go // Default returns an Engine instance with the Logger and Recovery middleware already attached. func Default() *Engine { debugPrintWARNINGDefault() // 忽略 engine := New() engine.Use(Logger(), Recovery()) // 暫時忽略 return engine }
其實單單看函數名,已經知道這是構造默認的 gin engine
,能夠看到 engine
是經過 New()
方法獲得的,咱們選忽略第一行和第三行。
// gin.go func New() *Engine { debugPrintWARNINGNew() // 忽略 engine := &Engine{ RouterGroup: RouterGroup{ // 路由組,後面再分析 Handlers: nil, basePath: "/", root: true, }, FuncMap: template.FuncMap{}, RedirectTrailingSlash: true, RedirectFixedPath: false, HandleMethodNotAllowed: false, ForwardedByClientIP: true, AppEngine: defaultAppEngine, // bool,是否爲默認處理 engine UseRawPath: false, UnescapePathValues: true, MaxMultipartMemory: defaultMultipartMemory, trees: make(methodTrees, 0, 9), delims: render.Delims{Left: "{{", Right: "}}"}, secureJsonPrefix: "while(1);", } engine.RouterGroup.engine = engine // 重點,將 engine 從新賦值給路由組對象中的 engine engine.pool.New = func() interface{} { // 重點,資源池 return engine.allocateContext() } return engine }
到這裏,咱們不用看 engine
結構的具體定義,也已經看出來比較多的信息了,主要用路由組和資源池,這兩個均可以分別展開寫一篇文章,因爲篇幅有限,這裏就先不介紹了。
值得注意的是這裏面有兩個地方很巧妙:
engine.RouterGroup.engine = engine
很明顯,路由組 RouterGroup
中還有個 engine
的指針對象,爲何要這麼設計呢?讀者們能夠思考一下。
engine.pool.New = func() interface{} { // 對象池 return engine.allocateContext() }
看下 engine.allocateContext()
方法:
func (engine *Engine) allocateContext() *Context { return &Context{engine: engine} }
能夠看到 engine
中包含了 pool
對象池,這個對象池是對 gin.Context
的重用,進一步減小開銷,關於 sync.pool
對象池我就不在這裏細說了, 後續再更新關於 sync.pool
的文章。
最後再來看看 engine
的結構:
type Engine struct { // 路由組 RouterGroup // 若是true,當前路由匹配失敗但將路徑最後的 / 去掉時匹配成功時自動匹配後者 // 好比:請求是 /foo/ 但沒有命中,而存在 /foo, // 對get method請求,客戶端會被301重定向到 /foo // 對於其餘method請求,客戶端會被307重定向到 /foo RedirectTrailingSlash bool // 若是true,在沒有處理者被註冊來處理當前請求時router將嘗試修復當前請求路徑 // 邏輯爲: // - 移除前面的 ../ 或者 // // - 對新的路徑進行大小寫不敏感的查詢 // 若是找到了處理者,請求會被301或307重定向 // 好比: /FOO 和 /..//FOO 會被重定向到 /foo // RedirectTrailingSlash 參數和這個參數獨立 RedirectFixedPath bool // 若是true,當路由沒有被命中時,去檢查是否有其餘method命中 // 若是命中,響應405 (Method Not Allowed) // 若是沒有命中,請求將由 NotFound handler 來處理 HandleMethodNotAllowed bool ForwardedByClientIP bool // #726 #755 If enabled, it will thrust some headers starting with // 'X-AppEngine...' for better integration with that PaaS. AppEngine bool // 若是true, url.RawPath 會被用來查找參數 UseRawPath bool // 若是true, path value 會被保留 // 若是 UseRawPath是false(默認),UnescapePathValues爲true // url.Path會被保留並使用 UnescapePathValues bool // Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm // method call. MaxMultipartMemory int64 delims render.Delims secureJsonPrefix string HTMLRender render.HTMLRender FuncMap template.FuncMap allNoRoute HandlersChain allNoMethod HandlersChain noRoute HandlersChain noMethod HandlersChain pool sync.Pool //每一個http method對應一棵樹 trees methodTrees }
上面提到過 ServeHTTP
這個方法,其中engine.handleHTTPRequest(c)
這行代碼就是具體的處理操做。
能夠看到 engine
的結構中有這麼一個字段 RedirectTrailingSlash
,在 Default()
初始化方法中爲 true
,我對此比較感興趣,你們也能夠根據註釋的意思來測試一下,最終會走到下面的代碼中:
func (engine *Engine) handleHTTPRequest(c *Context) { …… if httpMethod != "CONNECT" && rPath != "/" { if value.tsr && engine.RedirectTrailingSlash { // 這裏就是嘗試糾正請求路徑的函數 redirectTrailingSlash(c) return } if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { return } } …… }
上圖就是本文的核心了,能夠結合圖來理解一下 gin
啓動的過程及設計,下一篇會將 gin
的路由,敬請期待~
本系列 「拆輪子系列:gin 框架」 的第一篇就到這裏了,這麼通讀下來,發現 gin
框架的設計和實現真的太棒了,簡潔清晰,又不失巧妙,很適合你們也去閱讀學習一下,牆裂推薦!!!