最近將gin
的源碼看了一遍, 會(huì)用幾篇文章將gin的流程及流程做一個(gè)梳理, 下面進(jìn)入正題.
gin框架預(yù)覽
上圖大概是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)題:
- 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都提供了哪些有用的東西
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五層模型下
, 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ò)程的代碼了. 基本上是:
-
ln, err := net.Listen("tcp", addr)
做了初試化了socket
,bind
,listen
的操作. -
rw, e := l.Accept()
進(jìn)行accept, 等待客戶端進(jìn)行連接 -
go c.serve(ctx)
啟動(dòng)新的goroutine來(lái)處理本次請(qǐng)求. 同時(shí)主goroutine繼續(xù)等待客戶端連接, 進(jìn)行高并發(fā)操作 -
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)求.