Gin 源碼解讀, 基於 v1.5.0 版本.node
官方文檔上, 一個入門例子以下:git
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() // 監聽並在 0.0.0.0:8080 上啓動服務 }
看上去很是簡單, 首先進行初始化 gin.Default()
, 接着定義了一個叫作 /ping
的路由, 最後直接啓動了 r.Run()
.github
首先, 深刻查看下 gin.Default()
的過程:web
// 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 }
配合註釋, 咱們就明白了 Default
的主要功能是初始化 Engine
, 而後加載了兩個中間件, 用於日誌記錄和恢復.數組
而 Engine
其實是一個結構體, 也是 Gin 框架的核心, 看一下它的定義.服務器
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings. // Create an instance of Engine, by using New() or Default() type Engine struct { RouterGroup // Enables automatic redirection if the current route can't be matched but a // handler for the path with (without) the trailing slash exists. // For example if /foo/ is requested but a route only exists for /foo, the // client is redirected to /foo with http status code 301 for GET requests // and 307 for all other request methods. RedirectTrailingSlash bool // If enabled, the router tries to fix the current request path, if no // handle is registered for it. // First superfluous path elements like ../ or // are removed. // Afterwards the router does a case-insensitive lookup of the cleaned path. // If a handle can be found for this route, the router makes a redirection // to the corrected path with status code 301 for GET requests and 307 for // all other request methods. // For example /FOO and /..//Foo could be redirected to /foo. // RedirectTrailingSlash is independent of this option. RedirectFixedPath bool // If enabled, the router checks if another method is allowed for the // current route, if the current request can not be routed. // If this is the case, the request is answered with 'Method Not Allowed' // and HTTP status code 405. // If no other Method is allowed, the request is delegated to the 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 // If enabled, the url.RawPath will be used to find parameters. UseRawPath bool // If true, the path value will be unescaped. // If UseRawPath is false (by default), the UnescapePathValues effectively is true, // as url.Path gonna be used, which is already unescaped. 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 trees methodTrees }
註釋裏寫到 Engine
的初始化有兩種方式, Default()
已經看過了, 看一下 New()
:app
// New returns a new blank Engine instance without any middleware attached. // By default the configuration is: // - RedirectTrailingSlash: true // - RedirectFixedPath: false // - HandleMethodNotAllowed: false // - ForwardedByClientIP: true // - UseRawPath: false // - UnescapePathValues: true 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, UseRawPath: false, UnescapePathValues: true, MaxMultipartMemory: defaultMultipartMemory, trees: make(methodTrees, 0, 9), delims: render.Delims{Left: "{{", Right: "}}"}, secureJsonPrefix: "while(1);", } engine.RouterGroup.engine = engine engine.pool.New = func() interface{} { return engine.allocateContext() } return engine }
再看一下, 添加中間件的過程.框架
// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be // included in the handlers chain for every single request. Even 404, 405, static files... // For example, this is the right place for a logger or error management middleware. func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { engine.RouterGroup.Use(middleware...) engine.rebuild404Handlers() engine.rebuild405Handlers() return engine }
添加中間件, 其實是在 RouterGroup
上註冊, 那麼這 RouterGroup
又是什麼呢?less
// RouterGroup is used internally to configure router, a RouterGroup is associated with // a prefix and an array of handlers (middleware). type RouterGroup struct { Handlers HandlersChain basePath string engine *Engine root bool }
原來, RouterGroup
是用來配置路由的, 內部包含一個路由路徑 basePath
和中間件數組 Handlers
.
因此, 添加中間件只是在 Handlers
中新加一個元素:ide
// Use adds middleware to the group, see example code in GitHub. func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { group.Handlers = append(group.Handlers, middleware...) return group.returnObj() } func (group *RouterGroup) returnObj() IRoutes { if group.root { return group.engine } return group }
初始化的過程大致上就是如此.
web 服務器最主要的固然是定義路由和處理函數了.
r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) })
在前面, 咱們已經看過了 Engine
的定義, 注意看如下定義:
type Engine struct { RouterGroup
這顯示了 Engine
的內部使用了 RouterGroup
, 因此其實上各類 HTTP 方法都是註冊在 RouterGroup
上的. 具體看一下 GET
方法:
// GET is a shortcut for router.Handle("GET", path, handle). func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("GET", relativePath, handlers) }
經過註釋和代碼, 咱們能夠知道, GET
只是一個快捷方式, 其實全部的 HTTP 方法註冊都是由 router.Handle
處理的.
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
handle
的核心語句是 group.engine.addRoute(httpMethod, absolutePath, handlers)
.
先看一下 combineHandlers
, 能夠發現原來 handlers 是有限制的, 不能超過 63 個.
忽然以爲 Golang 中組合 slice 是有點蛋疼, 竟然要寫三行.
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len(group.Handlers) + len(handlers) if finalSize >= int(abortIndex) { panic("too many handlers") } mergedHandlers := make(HandlersChain, finalSize) copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) return mergedHandlers }
具體看一下 addRoute
有什麼操做.
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) root := engine.trees.get(method) if root == nil { root = new(node) root.fullPath = "/" engine.trees = append(engine.trees, methodTree{method: method, root: root}) } root.addRoute(path, handlers) }
略過前面的判斷以後, 能夠看到核心是操做 engine.trees
. 這用到了 httprouter.root.addRoute(path, handlers)
的內容有點多, 就不展開了.
總之, 到這裏, 路由已經註冊好了.
最後, 看一下 r.Run()
部分.
// Run attaches the router to a http.Server and starts listening and serving HTTP requests. // It is a shortcut for http.ListenAndServe(addr, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. 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 }
這是一個阻塞的方法, 除非發生錯誤. 內部使用 net/http
包的 ListenAndServe
函數.
上面咱們已經看到運行是經過 http.ListenAndServe(address, engine)
實現的,
這是內置的 net/http
包的內容, 看一下具體的定義:
func ListenAndServe(addr string, handler Handler) error
第二個參數的類型是 Handler
, 一猜就知道應該是接口類型, 看一下具體要實現什麼.
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
看一下 Engine
是如何實現 ServeHTTP
方法的.
// 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) c.Request = req c.reset() engine.handleHTTPRequest(c) engine.pool.Put(c) }
主要看一下 engine.handleHTTPRequest(c)
, 這用於處理 HTTP 請求.
func (engine *Engine) handleHTTPRequest(c *Context) { httpMethod := c.Request.Method rPath := c.Request.URL.Path unescape := false if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { rPath = c.Request.URL.RawPath unescape = engine.UnescapePathValues } rPath = cleanPath(rPath) // Find root of the tree for the given HTTP method t := engine.trees 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.handlers != nil { c.handlers = value.handlers c.Params = value.params c.fullPath = value.fullPath c.Next() c.writermem.WriteHeaderNow() return } if httpMethod != "CONNECT" && rPath != "/" { if value.tsr && engine.RedirectTrailingSlash { redirectTrailingSlash(c) return } if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { return } } break } if engine.HandleMethodNotAllowed { for _, tree := range engine.trees { if tree.method == httpMethod { continue } if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil { c.handlers = engine.allNoMethod serveError(c, http.StatusMethodNotAllowed, default405Body) return } } } c.handlers = engine.allNoRoute serveError(c, http.StatusNotFound, default404Body) }
代碼有點長, 不過有兩句註釋能夠幫咱們快速理解邏輯, 主要根據 HTTP 方法和路徑從 engine.trees
找到
對應的 handlers, 當能找到時:
if value.handlers != nil { c.handlers = value.handlers c.Params = value.params c.fullPath = value.fullPath c.Next() c.writermem.WriteHeaderNow() return }
重點關注下 c.Next()
:
// Next should be used only inside middleware. // It executes the pending handlers in the chain inside the calling handler. // See example in GitHub. func (c *Context) Next() { c.index++ for c.index < int8(len(c.handlers)) { c.handlers[c.index](c) c.index++ } }
由此, 就執行 Context 中的 handlers. 注意到, handlers 中包括了中間件和主處理函數,
所以就完成了路由的處理.
固然, 也有找不到對應的路由的時候, 這可能有多種緣由, 好比 HTTP 方法不存在, 或者是路徑不存在.
調用 Next() 的過程當中涉及到了中間件的原理, 下面具體講一講.
先看一下如何定義並添加中間件, 來自官方文檔:
func Logger() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now() // Set example variable c.Set("example", "12345") // before request c.Next() // after request latency := time.Since(t) log.Print(latency) // access the status we are sending status := c.Writer.Status() log.Println(status) } } func main() { r := gin.New() r.Use(Logger()) r.GET("/test", func(c *gin.Context) { example := c.MustGet("example").(string) // it would print: "12345" log.Println(example) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") }
從上面的例子中能夠看出, 定義中間件和定義主處理函數沒什麼區別, 方法定義都是 gin.HandlerFunc
:
type HandlerFunc func(*Context)
中間件中也調用了 c.Next()
, 劃分了請求的過程, c.Next()
運行前是請求前(before request), 運行後是請求後(after request).
從上面的流程圖中假設註冊兩個處理函數, 第一個是 log 中間件, 用於日誌記錄, 另外一個是該路徑的主處理函數.
當接收到請求時, 進入不少個 Next 中, 這是由於中間件可能也會調用 c.Next()
. 能夠將 c.Next()
理解爲控制流轉移, 每當運行 c.Next()
, 其實是運行下一個 handler. 有點相似遞歸時的調用棧.
Gin 的基本流程就是這樣的, 粗看代碼, 以爲仍是挺清晰的. 更多內容還有待挖掘.