<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>

          gin 源碼閱讀之 gin 與 net/http 的關(guān)系

          共 12173字,需瀏覽 25分鐘

           ·

          2021-09-27 10:57

          gin 是目前 Go 里面使用最廣泛的框架之一了,弄清楚 gin 框架的原理,有助于我們更好的使用 gin。這個(gè)系列 gin 源碼閱讀會(huì)逐步講明白 gin 的原理,歡迎關(guān)注后續(xù)文章。

          gin 概覽

          想弄清楚 gin, 需要弄明白以下幾個(gè)問(wèn)題:

          • request數(shù)據(jù)是如何流轉(zhuǎn)的
          • gin框架到底扮演了什么角色
          • 請(qǐng)求從gin流入net/http, 最后又是如何回到gin中
          • gin的context為何能承擔(dān)起來(lái)復(fù)雜的需求
          • gin的路由算法
          • gin的中間件是什么
          • gin的Engine具體是個(gè)什么東西
          • net/http的requeset, response都提供了哪些有用的東西

          gin的官方第一個(gè)demo入手.

          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() // listen and serve on 0.0.0.0:8080
          }

          r.Run() 的源碼:

          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
          }

          看到開(kāi)始調(diào)用的是 http.ListenAndServe(address, engine), 這個(gè)函數(shù)是net/http的函數(shù), 然后請(qǐng)求數(shù)據(jù)就在net/http開(kāi)始流轉(zhuǎn).

          Request 數(shù)據(jù)是如何流轉(zhuǎn)的

          先不使用gin, 直接使用net/http來(lái)處理http請(qǐng)求

          func main() {
              http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
                  w.Write([]byte("Hello World"))
              })

              if err := http.ListenAndServe(":8000", nil); err != nil {
                  fmt.Println("start http server fail:", err)
              }
          }

          在瀏覽器中輸入localhost:8000, 會(huì)看到Hello World. 下面利用這個(gè)簡(jiǎn)單demo看下request的流轉(zhuǎn)流程.

          HTTP是如何建立起來(lái)的

          簡(jiǎn)單的說(shuō)一下http請(qǐng)求是如何建立起來(lái)的(需要有基本的網(wǎng)絡(luò)基礎(chǔ), 可以找相關(guān)的書(shū)籍查看, 推薦看UNIX網(wǎng)絡(luò)編程卷1:套接字聯(lián)網(wǎng)API)

          TCP/IP 五層模型
          socket建立過(guò)程

          TCP/IP五層模型下, HTTP位于應(yīng)用層, 需要有傳輸層來(lái)承載HTTP協(xié)議. 傳輸層比較常見(jiàn)的協(xié)議是TCP,UDP, SCTP等. 由于UDP不可靠, SCTP有自己特殊的運(yùn)用場(chǎng)景, 所以一般情況下HTTP是由TCP協(xié)議承載的(可以使用wireshark抓包然后查看各層協(xié)議)

          使用TCP協(xié)議的話, 就會(huì)涉及到TCP是如何建立起來(lái)的. 面試中能夠常遇到的名詞三次握手, 四次揮手就是在這里產(chǎn)生的. 具體的建立流程就不在陳述了, 大概流程就是圖中左半邊

          所以說(shuō), 要想能夠?qū)蛻舳薶ttp請(qǐng)求進(jìn)行回應(yīng)的話, 就首先需要建立起來(lái)TCP連接, 也就是socket. 下面要看下net/http是如何建立起來(lái)socket?

          net/http 是如何建立 socket 的

          從圖上可以看出, 不管server代碼如何封裝, 都離不開(kāi)bind,listen,accept這些函數(shù). 就從上面這個(gè)簡(jiǎn)單的demo入手查看源碼.

          func main() {
              http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
                  w.Write([]byte("Hello World"))
              })

              if err := http.ListenAndServe(":8000", nil); err != nil {
                  fmt.Println("start http server fail:", err)
              }
          }

          注冊(cè)路由

          http.HandleFunc("/"func(w http.ResponseWriter, r *http.Request) {
              w.Write([]byte("Hello World"))
          })

          這段代碼是在注冊(cè)一個(gè)路由及這個(gè)路由的handler到DefaultServeMux

          // server.go:L2366-2388
          func (mux *ServeMux) Handle(pattern string, handler Handler) {
              mux.mu.Lock()
              defer mux.mu.Unlock()

              if pattern == "" {
                  panic("http: invalid pattern")
              }
              if handler == nil {
                  panic("http: nil handler")
              }
              if _, exist := mux.m[pattern]; exist {
                  panic("http: multiple registrations for " + pattern)
              }

              if mux.m == nil {
                  mux.m = make(map[string]muxEntry)
              }
              mux.m[pattern] = muxEntry{h: handler, pattern: pattern}

              if pattern[0] != '/' {
                  mux.hosts = true
              }
          }

          可以看到這個(gè)路由注冊(cè)太過(guò)簡(jiǎn)單了, 也就給gin, iris, echo等框架留下了擴(kuò)展的空間, 后面詳細(xì)說(shuō)這個(gè)東西

          服務(wù)監(jiān)聽(tīng)及響應(yīng)

          上面路由已經(jīng)注冊(cè)到net/http了, 下面就該如何建立socket了, 以及最后又如何取到已經(jīng)注冊(cè)到的路由, 將正確的響應(yīng)信息從handler中取出來(lái)返回給客戶端

          1.創(chuàng)建 socket

          if err := http.ListenAndServe(":8000", nil); err != nil {
              fmt.Println("start http server fail:", err)
          }
          // net/http/server.go:L3002-3005
          func ListenAndServe(addr string, handler Handler) error {
              server := &Server{Addr: addr, Handler: handler}
              return server.ListenAndServe()
          }
          // net/http/server.go:L2752-2765
          func (srv *Server) ListenAndServe() error {
              // ... 省略代碼
              ln, err := net.Listen("tcp", addr) // <-----看這里listen
              if err != nil {
                return err
              }
              return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
          }

          2.Accept 等待客戶端鏈接

          // net/http/server.go:L2805-2853
          func (srv *Server) Serve(l net.Listener) error {
              // ... 省略代碼
              for {
                rw, e := l.Accept() // <----- 看這里accept
                if e != nil {
                  select {
                  case <-srv.getDoneChan():
                    return ErrServerClosed
                  default:
                  }
                  if ne, ok := e.(net.Error); ok && ne.Temporary() {
                    if tempDelay == 0 {
                      tempDelay = 5 * time.Millisecond
                    } else {
                      tempDelay *= 2
                    }
                    if max := 1 * time.Second; tempDelay > max {
                      tempDelay = max
                    }
                    srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
                    time.Sleep(tempDelay)
                    continue
                  }
                  return e
                }
                tempDelay = 0
                c := srv.newConn(rw)
                c.setState(c.rwc, StateNew) // before Serve can return
                go c.serve(ctx) // <--- 看這里
              }
          }

          3. 提供回調(diào)接口 ServeHTTP

          // net/http/server.go:L1739-1878
          func (c *conn) serve(ctx context.Context) {
              // ... 省略代碼
              serverHandler{c.server}.ServeHTTP(w, w.req)
              w.cancelCtx()
              if c.hijacked() {
                return
              }
              w.finishRequest()
              // ... 省略代碼
          }
          // net/http/server.go:L2733-2742
          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{}
              }
              handler.ServeHTTP(rw, req)
          }
          // net/http/server.go:L2352-2362
          func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
              if r.RequestURI == "*" {
                if r.ProtoAtLeast(1, 1) {
                  w.Header().Set("Connection""close")
                }
                w.WriteHeader(StatusBadRequest)
                return
              }
              h, _ := mux.Handler(r) // <--- 看這里
              h.ServeHTTP(w, r)
          }

          4. 回調(diào)到實(shí)際要執(zhí)行的 ServeHTTP

          // net/http/server.go:L1963-1965
          func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
             f(w, r)
          }

          這基本是整個(gè)過(guò)程的代碼了.

          1. ln, err := net.Listen("tcp", addr)做了初試化了socket, bind, listen的操作.
          2. rw, e := l.Accept()進(jìn)行accept, 等待客戶端進(jìn)行連接
          3. go c.serve(ctx) 啟動(dòng)新的goroutine來(lái)處理本次請(qǐng)求. 同時(shí)主goroutine繼續(xù)等待客戶端連接, 進(jìn)行高并發(fā)操作
          4. h, _ := mux.Handler(r) 獲取注冊(cè)的路由, 然后拿到這個(gè)路由的handler, 然后將處理結(jié)果返回給客戶端

          從這里也能夠看出來(lái), net/http基本上提供了全套的服務(wù).

          為什么會(huì)出現(xiàn)很多go框架

          // net/http/server.go:L2218-2238
          func (mux *ServeMux) match(path string) (h Handler, pattern string) {
              // Check for exact match first.
              v, ok := mux.m[path]
              if ok {
                  return v.h, v.pattern
              }

              // Check for longest valid match.
              var n = 0
              for k, v := range mux.m {
                if !pathMatch(k, path) {
                    continue
                }
                if h == nil || len(k) > n {
                    n = len(k)
                    h = v.h
                    pattern = v.pattern
                }
              }
              return
          }

          從這段函數(shù)可以看出來(lái), 匹配規(guī)則過(guò)于簡(jiǎn)單, 當(dāng)能匹配到路由的時(shí)候就返回其對(duì)應(yīng)的handler, 當(dāng)不能匹配到時(shí)就返回/. net/http的路由匹配根本就不符合 RESTful 的規(guī)則,遇到稍微復(fù)雜一點(diǎn)的需求時(shí),這個(gè)簡(jiǎn)單的路由匹配規(guī)則簡(jiǎn)直就是噩夢(mèng)。

          所以基本所有的go框架干的最主要的一件事情就是重寫(xiě)net/http的route。我們直接說(shuō) gin就是一個(gè) httprouter 也不過(guò)分, 當(dāng)然gin也提供了其他比較主要的功能, 后面會(huì)一一介紹。

          綜述, net/http基本已經(jīng)提供http服務(wù)的70%的功能, 那些號(hào)稱(chēng)賊快的go框架, 基本上都是提供一些功能, 讓我們能夠更好的處理客戶端發(fā)來(lái)的請(qǐng)求. 如果你有興趣的話,也可以基于 net/http 做一個(gè) Go 框架出來(lái)。



          推薦閱讀


          福利

          我為大家整理了一份從入門(mén)到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門(mén)看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù) ebook 獲??;還可以回復(fù)「進(jìn)群」,和數(shù)萬(wàn) Gopher 交流學(xué)習(xí)。

          瀏覽 52
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  三级电影久久 | 就爱搞搞网 | 婷婷五月天黄色电影 | 日日夜夜久久视频久久视频 | 大长腿美女被操 |