Gin是一個比較輕量級的http框架,主要是提供了幾個便於使用的功能:node
性能方面:git
使用方便也是比較簡單的,下面有一個很簡單的例子github
package main import ( "fmt" "github.com/gin-gonic/gin" "test/gin/middleware/model" ) func main() { //建立router router := gin.Default() //建立組 group := router.Group("/api") //爲組加中間件 group.Use(func(context *gin.Context) { fmt.Println("api group url:", context.Request.URL.String()) }) //爲組加路由方法 group.GET("/test", func(context *gin.Context) { context.JSON(200, model.Message{Message:"ok"}) }) //運行 router.Run(":3333") }
例子中是一個最簡單的Gin框架的應用。建立了一個engine,建立了組而且爲組添加了中間件以後在這個group下的路由方法都將使用這個中間件,方便對api最系統的管理對不一樣的api作不一樣的處理。
在Terminal中訪問能夠看到下面的結果json
curl http://localhost:3333/api/test {"Message":"ok"}panleiMacBook-Pro:test
首先咱們先看例子中的gin.Default() 返回的engine對象,這是Gin的主要對象。只對最主要的屬性加了註釋api
type Engine struct { //路由組 RouterGroup RedirectTrailingSlash bool RedirectFixedPath bool HandleMethodNotAllowed bool ForwardedByClientIP bool AppEngine bool UseRawPath bool UnescapePathValues bool MaxMultipartMemory int64 delims render.Delims secureJsonPrefix string HTMLRender render.HTMLRender FuncMap template.FuncMap allNoRoute HandlersChain allNoMethod HandlersChain noRoute HandlersChain noMethod HandlersChain // 對象池 用來建立上下文context pool sync.Pool //記錄路由方法的 好比GET POST 都會是數組中的一個 每一個方法對應一個基數樹的一個root的node trees methodTrees }
而後咱們來看Default方法,其實很簡單就是建立一個engine對象而且添加默認的兩個中間件,一個是作log顯示,顯示每次請求能夠再console中看到。每次建立engine對象的時候回默認的添加一個routergroup地址爲默認的"/" 代碼以下:數組
func Default() *Engine { debugPrintWARNINGDefault() engine := New() engine.Use(Logger(), Recovery()) return engine } //new方法中默認的 添加了routergroup路由 「/」 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 }
在看中間件以前還有一個重要的對象context 上下文對象
這個對象中存放了 engine指針、請求的request對象、返回的responsewriter對象還有一些參數等對象,這個context將在請求一開始就被建立一直貫穿整個執行過程,包括中間件,路由等。最後的返回值能夠再responseWriter寫,最終就會返回給客戶端。app
type Context struct { writermem responseWriter Request *http.Request Writer ResponseWriter Params Params handlers HandlersChain index int8 engine *Engine // Keys is a key/value pair exclusively for the context of each request. Keys map[string]interface{} // Errors is a list of errors attached to all the handlers/middlewares who used this context. Errors errorMsgs // Accepted defines a list of manually accepted formats for content negotiation. Accepted []string }
接下來看得是添加中間件use方法框架
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { //調用routegroup的use方法 engine.RouterGroup.Use(middleware...) engine.rebuild404Handlers() engine.rebuild405Handlers() return engine } func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { //爲group的handlers添加中間件 group.Handlers = append(group.Handlers, middleware...) return group.returnObj() }
添加爲中間件以後就是添加正常路由,Gin中提供了GET、POST、DELETE更各類方法,咱們就只看get方法其他的都是相同的處理方式。總結下來就是把group和傳入的handler合併,而且計算出路徑存入到tree中等客戶端的調用。curl
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 { //計算路徑地址,好比group地址是 router.Group("/api") //結果爲/api/test/ 就是最終計算出來的結果 使用path.join 方法拼接 其中加了一些判斷 absolutePath := group.calculateAbsolutePath(relativePath) //把group中的handler和傳入的handler合併 handlers = group.combineHandlers(handlers) //把方法 路徑 和處理方法做爲node 加入到基數樹種,基數樹在下次單獨學習分析 group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() } 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 }
最後咱們須要看run方法,以前的都是準備工做或者說是設置路由中間件。等run方法執行的時候則是服務真正啓動起來。 代碼很簡單,幾乎不須要註釋是調用gohttp包的ListenAndServe方法把engine傳入而後http包中會有一個for邏輯不停的監聽這個端口號的全部請求。post
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 } func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
那麼客戶端最終請求以後會走到那些代碼,又是怎麼找到路由而且調用一個個中間件的呢?其實engine是繼承了Handler 這個接口(能夠看下面代碼),咱們知道若是不適用Gin框架直接使用http包咱們全部的路由就是直接繼承這個接口因此對這個接口咱們是很熟悉的。
下面的serveHTTP 就是Gin的方法 最主要流程就是從tree中獲取到路由,而後依次執行handler最終處理完成返回給客戶端。
type Handler interface { ServeHTTP(ResponseWriter, *Request) } func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { //從對象池中獲取context對象,這就是最初咱們看到的一種優化性能的一種方式 c := engine.pool.Get().(*Context) //重置writer中的一些值 c.writermem.reset(w) 把request放到context中 c.Request = req c.reset() //調用處理方法 engine.handleHTTPRequest(c) //處理完成把context對象放回到對象池 engine.pool.Put(c) } func (engine *Engine) handleHTTPRequest(c *Context) { //獲取請求方法和路徑 httpMethod := c.Request.Method path := c.Request.URL.Path unescape := false if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { path = c.Request.URL.RawPath unescape = engine.UnescapePathValues } //根據基數樹的特性尋找方法發 並調用next方法 依次執行handler 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 handlers, params, tsr := root.getValue(path, c.Params, unescape) if handlers != nil { c.handlers = handlers c.Params = params c.Next() c.writermem.WriteHeaderNow() return } if httpMethod != "CONNECT" && path != "/" { if 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 handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil { c.handlers = engine.allNoMethod serveError(c, http.StatusMethodNotAllowed, default405Body) return } } } c.handlers = engine.allNoRoute serveError(c, http.StatusNotFound, default404Body) }
從上面的流程咱們能夠看到,其實Gin框架就是對go http包的一次封裝,加入了group和tree。讓咱們能夠更簡單方便的使用http包。其實Gin還有不少其餘功能的源碼包括參數分析 json解析等等,此次學習的只是Gin的主要流程。Gin中tree是一個亮點,是的它在查找路由性能方面有很大的優點,下一篇文章會主要學習tree(基數樹)。