<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          golang gin大行其道!

          共 5890字,需瀏覽 12分鐘

           ·

          2020-09-19 08:20

          前言


          很多人已經(jīng)在api接口項目這塊,已經(jīng)用上了gin,我自己也用上了,感覺挺好用的,就寫了這篇文章來分享下我對gin的理解和拾遺。gin對于我的理解還不能算是一個api框架,因為它是基于net/http包來處理http請求處理的,包括監(jiān)聽連接,解析請求,處理響應都是net/http包完成的,gin主要是做了路由前綴樹的構(gòu)建,包裝http.request,http.response,還有一些快捷輸出響應內(nèi)容格式的工具功能(json.Render,xml.Render,text.Render,proto.Render)等,接下來主要從兩個方面去分析gin。


          • gin構(gòu)建路由

          • gin處理請求

          一,gin構(gòu)建路由

          下面是gin用來構(gòu)建路由用到的結(jié)構(gòu)代碼
          type Engine struct { RouterGroup trees methodTrees 省略代碼}// 每個http.method (GET,POST,HEAD,PUT等9種)都會構(gòu)建成單獨的前綴樹type methodTree struct { // 方法: GET,POST等 method string // 對應方法的前綴樹的根節(jié)點 root *node}
          //GET,POST前綴樹等構(gòu)成的切片type methodTrees []methodTree
          type node struct { // 節(jié)點的當前路徑 path string // 子節(jié)點的首字母構(gòu)成的字符串,數(shù)量和位置對應children切片 indices string // 子節(jié)點 children []*node // 當前匹配到的路由,處理的handler handlers HandlersChain // 統(tǒng)計子節(jié)點優(yōu)先級 priority uint32 // 節(jié)點類型 nType nodeType maxParams uint8 wildChild bool // 當前節(jié)點的全路徑,從root到當前節(jié)點的全路徑 fullPath string}


          用戶定義路由router := gin.New()router.GET("banner/detail", PkgHandler)router.PrintTrees()router.GET("/game/detail", PkgHandler)router.PrintTrees()router.GET("/geme/detail", PkgHandler)

          router.GET 調(diào)用鏈這里提一下HandlerFunc,就是gin常說的中間件,是一個函數(shù)類型,只要實現(xiàn)該函數(shù)類型就可以用作中間件來處理邏輯
          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) handlers = group.combineHandlers(handlers) 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) // 根據(jù)method取出當前方法前綴樹的root root := engine.trees.get(method) if root == nil { root = new(node) root.fullPath = "/" engine.trees = append(engine.trees, methodTree{method: method, root: root}) } // 這里就是構(gòu)建路由前綴樹的邏輯(避免閱讀不友好,這里就不繼續(xù)貼下去了) root.addRoute(path, handlers)}
          1. ?/banner/detail(路由最前面如果沒有/開頭的話,gin會自動補上/)

          2. /game/detail

          3. /geme/detail?

          上面三個路由地址,構(gòu)建路由前綴樹的過程(這里不討論:和 *)



          最后的路由樹結(jié)構(gòu)就是上面這個樣子?

          gin的RouterGroup路由組的概念(group和use方法),主要是給后面添加路由用來傳遞path和handlers和繼承之前path和handlers,這里不細講


          二,gin處理請求

          先簡述下net/http包處理的整個流程1. 解析數(shù)據(jù)包,解析成http請求協(xié)議,存在http.request, 包裝請求頭,請求體,當前連接conn【read(conn) 讀取數(shù)據(jù)】2. 外部實現(xiàn)handler接口,調(diào)用外部邏輯處理請求(這里外部就是gin)3. 根據(jù)用戶邏輯,返回http響應給客戶端  存在http.response,包裝響應頭,響應體,當前連接conn【write(conn) 返回數(shù)據(jù)】
          這是net/http包提供給外部,用于處理http請求的handler接口,只要外部實現(xiàn)了此接口,并且傳遞給http.server.Handler,就可以執(zhí)行用戶handler邏輯,gin的Engine實現(xiàn)了Handler 接口,所以gin就介入了整個http請求流程中的用戶邏輯那部分。即上面的第二點。

          type Handler interface { ServeHTTP(ResponseWriter, *Request)}
          func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe()}
          serverHandler{c.server}.ServeHTTP(w, w.req)
          func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { handler := sh.srv.Handler if handler == nil { handler = DefaultServeMux } if req.RequestURI == "*" && req.Method == "OPTIONS" { handler = globalOptionsHandler{} } //在這里最終會調(diào)用用戶傳進來的handler接口的實現(xiàn),并且把rw = http.response,req = http.request 傳遞到gin處理邏輯里面去,這樣gin既能讀取http請求內(nèi)容,也能操作gin輸出到客戶端 handler.ServeHTTP(rw, req)}
          // ServeHTTP conforms to the http.Handler interface.// gin.Engine實現(xiàn)了http.Handler接口,可以處理請求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)}


          ?

          所以,gin處理請求的入口ServeHTTP這里

          1. 每個請求都會用到單獨gin.context,包裝http.response, http.request,通過在中間件中傳遞gin.context,從而用戶可以拿到請求相關信息,根據(jù)自己邏輯可以處理請求和響應,context也用了pool池化,減少內(nèi)存分配

          2. 根據(jù)上面?zhèn)鬟f的請求路徑,和method,在對應方法的路由前綴樹上查詢到節(jié)點,即就找到要執(zhí)行的handlers,就執(zhí)行用戶定義的中間件邏輯(前綴樹的查詢也跟構(gòu)建流程類似,可以參考上面的圖)


          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  }
          if engine.RemoveExtraSlash { rPath = cleanPath(rPath) } fmt.Printf("httpMethod: %s, rPath: %s\n", httpMethod, 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 // 根據(jù)請求路徑獲取對應方法樹上的節(jié)點 value := root.getValue(rPath, c.Params, unescape) if value.handlers != nil { // 獲取對應節(jié)點上的中間件(即handler) c.handlers = value.handlers fmt.Printf("c.handlers: %d\n", len(c.handlers)) c.Params = value.params c.fullPath = value.fullPath c.Next() c.writermem.WriteHeaderNow() return } 省略代碼 } 省略代碼}handlers有2種方式1. 在handler中不手動調(diào)用c.next的話,類似于隊列,先進先執(zhí)行2. 如果手動調(diào)用c.next的話,類似于洋蔥模型,包裹剝開概念的執(zhí)行func (c *Context) Next() { //c.index從-1開始 c.index++ for c.index < int8(len(c.handlers)) { c.handlers[c.index](c) c.index++ }}中斷整個調(diào)用鏈,從當前函數(shù)返回func (c *Context) Abort() { //直接將中間件索引改成最大限制的值,從而退出for循環(huán) c.index = abortIndex }

          貼下圖,方便理解這個中間件是怎么依次執(zhí)行的



          gin執(zhí)行完handlers之后,就回歸到net/http包里面,就會finishRequest()


          serverHandler{c.server}.ServeHTTP(w, w.req)w.cancelCtx()if c.hijacked() {   return}flush數(shù)據(jù), write到connw.finishRequest()


          總結(jié)

          1. gin存儲路由結(jié)構(gòu),用到了類似前綴樹的數(shù)據(jù)結(jié)構(gòu),在存儲方面可以共用前綴節(jié)省空間,但是查找方面應該不是很優(yōu)秀,為什么不用普通的hash結(jié)構(gòu),key-value方式,存儲路由和handlers,因為畢竟一個項目里面,定義的路由路徑應該不會很多,使用hash存儲應該不會占用很多內(nèi)存。

          但是如果每次請求都要去查找一下路由前綴樹的話會比hash結(jié)構(gòu)慢很多(請求量越大應該越明顯)。

          2. gin是基于net/http官方包來處理http請求整個流程

          3. gin的中間件流程很好用



          推薦閱讀


          學習交流 Go 語言,掃碼回復「進群」即可


          站長 polarisxu

          自己的原創(chuàng)文章

          不限于 Go 技術(shù)

          職場和創(chuàng)業(yè)經(jīng)驗


          Go語言中文網(wǎng)

          每天為你

          分享 Go 知識

          Go愛好者值得關注




          瀏覽 109
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久,操电影网 | 波多野结衣系列在线天堂 | 麻豆视频一区 | 99热99在线观看 | 久久精品内射 |