gin源碼閱讀之一 -- net/http的大概流程

最近將gin的源碼看了一遍, 會(huì)用幾篇文章將gin的流程及流程做一個(gè)梳理, 下面進(jìn)入正題.

gin框架預(yù)覽

gin

上圖大概是gin里面比較重要的模塊. 從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).

所以, gin源碼閱讀系列就是要弄明白以下幾個(gè)問(wèn)題:

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

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)

socket

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)返回給客戶端

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)})
}
// 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) // <--- 看這里
    }
}
// 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)
}

// 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的路由匹配無(wú)法滿足復(fù)雜的需求開(kāi)發(fā). 所以基本所有的go框架干的最主要的一件事情就是重寫(xiě)net/http的route

所以我們直接說(shuō)gin就是一個(gè)httprouter也不過(guò)分, 當(dāng)然gin也提供了其他比較主要的功能, 后面會(huì)一一介紹

還有一個(gè)go框架要實(shí)現(xiàn)的東西是http.ResponseWriter

綜述, net/http基本已經(jīng)提供http服務(wù)的70%的功能, 那些號(hào)稱賊快的go框架, 基本上都是提供一些功能, 讓我們能夠更好的處理客戶端發(fā)來(lái)的請(qǐng)求.

參考資料

  1. Go 開(kāi)發(fā) HTTP
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市器钟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖震桶,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蕊蝗,死亡現(xiàn)場(chǎng)離奇詭異如迟,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)辰企,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)况鸣,“玉大人牢贸,你說(shuō)我怎么就攤上這事「渑酰” “怎么了潜索?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)懂酱。 經(jīng)常有香客問(wèn)我竹习,道長(zhǎng),這世上最難降的妖魔是什么玩焰? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任由驹,我火速辦了婚禮,結(jié)果婚禮上昔园,老公的妹妹穿的比我還像新娘蔓榄。我一直安慰自己,他們只是感情好默刚,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布甥郑。 她就那樣靜靜地躺著,像睡著了一般荤西。 火紅的嫁衣襯著肌膚如雪澜搅。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,816評(píng)論 1 290
  • 那天邪锌,我揣著相機(jī)與錄音勉躺,去河邊找鬼。 笑死觅丰,一個(gè)胖子當(dāng)著我的面吹牛饵溅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播妇萄,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蜕企,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼咬荷!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起轻掩,我...
    開(kāi)封第一講書(shū)人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤幸乒,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后唇牧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體罕扎,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年奋构,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了壳影。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡弥臼,死狀恐怖宴咧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情径缅,我是刑警寧澤掺栅,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站纳猪,受9級(jí)特大地震影響氧卧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜氏堤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一沙绝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鼠锈,春花似錦闪檬、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至同欠,卻和暖如春样傍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背铺遂。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工衫哥, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人襟锐。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓炕檩,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親捌斧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子笛质,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容