淺析Golang中的net/http路由注冊(cè)與請(qǐng)求處理
注冊(cè)路由
初學(xué)web時(shí),我們常用http.HandleFunc()來(lái)注冊(cè)路由,然后用http.ListenAndServe()來(lái)啟動(dòng)服務(wù),接下來(lái)讓我們分析一下http包內(nèi)部是如何注冊(cè)路由的。
除了常用的http.HandleFunc()可以注冊(cè)路由,還有http.Handle可以注冊(cè),先看一下源碼。
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) } func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }
對(duì)比一下兩個(gè)函數(shù)的不同:
1、分別調(diào)用了DefaultServeMux的Handle和HandleFunc方法。
2、handler參數(shù)類型分別為http.Handler接口,和func(ResponseWriter, *Request)類型。說(shuō)明一下,DefaultServerMux是http包的全局變量,如果不使用默認(rèn)的復(fù)用器
接下來(lái)看一下Handle和HandleFunc的主要源碼,和Handler接口
// 注冊(cè)路由 func (mux *ServeMux) Handle(pattern string, handler Handler) { mux.mu.Lock() defer mux.mu.Unlock() ... if mux.m == nil { mux.m = make(map[string]muxEntry) } e := muxEntry{h: handler, pattern: pattern} mux.m[pattern] = e ... } // 調(diào)用Handle注冊(cè)路由 func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { if handler == nil { panic("http: nil handler") } mux.Handle(pattern, HandlerFunc(handler)) } // 實(shí)現(xiàn)了Handler的函數(shù)類型 type HandlerFunc func(ResponseWriter, *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
可以看到ServeMux.Handle最終實(shí)現(xiàn)了路由的注冊(cè),mux.m記錄了路由與處理器的映射;而ServeMux.HandleFunc將handler參數(shù)轉(zhuǎn)換成了HandlerFunc類型,然后調(diào)用ServeMux.Handle。
那么問(wèn)題來(lái)了,ServeMux.Handle的handler參數(shù)是Handler接口類型,我們調(diào)用http.HandleFunc()傳入的處理器函數(shù)簽名是func(ResponseWriter, *Request),我們傳入的函數(shù)咋就實(shí)現(xiàn)了Handler接口?
答案就在于HandlerFunc類型,它實(shí)現(xiàn)了Handler接口。我們傳入的處理器函數(shù)與HandlerFunc類型函數(shù)簽名是一致的,如果沒(méi)有HandlerFunc,要注冊(cè)函數(shù)的話,我們就要自己定義結(jié)構(gòu)體,寫ServeHTTP方法,實(shí)現(xiàn)Handler接口,而有了HandlerFunc我們就可以把這一步省去了,在設(shè)計(jì)模式中,這叫裝飾器模式。
處理請(qǐng)求
ServerMux
使用http.HandleFunc和http.Handle注冊(cè)的路由都注冊(cè)到了DefaultServerMux,它也實(shí)現(xiàn)了handler接口,那讓我們來(lái)看一下ServerMux的ServeHTTP方法。
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) }
mux.Handler()中會(huì)調(diào)用mux.match(),從ServeMux.m(map[string]muxEntry類型),獲取路由和對(duì)應(yīng)處理器函數(shù)(我們傳入的),然后就可以調(diào)用h.ServeHTTP(w,r)來(lái)處理對(duì)應(yīng)的請(qǐng)求。
現(xiàn)在已得知我們傳入的處理函數(shù)是被ServeMux.ServeHTTP()調(diào)用,那ServerMus.ServeHTTP()又是怎么被調(diào)用的呢?接下來(lái)跟蹤一下http.ListenAndServe()的源碼。
Server
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
用http.ListenAndServe()啟動(dòng)服務(wù)的話,handler參數(shù)一般傳nil,使用DefaultServerMux做處理器,下面的分析都以此為前提。
在函數(shù)內(nèi)部幫我們創(chuàng)建了一個(gè)Server結(jié)構(gòu)體,如果想更靈活地使用,可以自己創(chuàng)建Server結(jié)構(gòu)體,調(diào)用server.ListenAndServe()。
func (srv *Server) ListenAndServe() error { if srv.shuttingDown() { return ErrServerClosed } addr := srv.Addr if addr == "" { addr = ":http" } ln, err := net.Listen("tcp", addr) // 監(jiān)聽地址 if err != nil { return err } return srv.Serve(ln) }
Server.ListenAndServe()中完成了監(jiān)聽地址的綁定,然后再調(diào)用Server.Serve()
func (srv *Server) Serve(l net.Listener) error { ... ctx := context.WithValue(baseCtx, ServerContextKey, srv) for { rw, err := l.Accept() ... connCtx := ctx if cc := srv.ConnContext; cc != nil { connCtx = cc(connCtx, rw) if connCtx == nil { panic("ConnContext returned nil") } } c := srv.newConn(rw) c.setState(c.rwc, StateNew, runHooks) go c.serve(connCtx) } }
Server.Serve中開啟了一個(gè)for循環(huán)來(lái)接收連接,并為每一個(gè)連接創(chuàng)建contexxt,開一個(gè)協(xié)程繼續(xù)處理。
func (c *conn) serve(ctx context.Context) { c.remoteAddr = c.rwc.RemoteAddr().String() ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr()) var inFlightResponse *response defer func() { if err := recover(); err != nil && err != ErrAbortHandler { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf) } if inFlightResponse != nil { inFlightResponse.cancelCtx() } if !c.hijacked() { if inFlightResponse != nil { inFlightResponse.conn.r.abortPendingRead() inFlightResponse.reqBody.Close() } c.close() c.setState(c.rwc, StateClosed, runHooks) } }() ... for { w, err := c.readRequest(ctx) ... inFlightResponse = w serverHandler{c.server}.ServeHTTP(w, w.req) // 這里并不是Handler接口的ServeHTTP方法 inFlightResponse = nil w.cancelCtx() if c.hijacked() { return } ... } ... }
在http包server.go 1991行,嵌套了Server結(jié)構(gòu)體的serverHandler結(jié)構(gòu)體調(diào)用ServeHTTP方法,需要注意的是這并不是Handler接口的ServeHTTP方法,我們傳入的處理器函數(shù)的調(diào)用還在其內(nèi)部。
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { handler := sh.srv.Handler if handler == nil { handler = DefaultServeMux // 如果在創(chuàng)建Server時(shí),Handler參數(shù)為nil,就使用DefaultServeMux // 通過(guò)http.ListenAndServe開啟服務(wù)一般都用DefaultServeMux } if req.RequestURI == "*" && req.Method == "OPTIONS" { handler = globalOptionsHandler{} } if req.URL != nil && strings.Contains(req.URL.RawQuery, ";") { var allowQuerySemicolonsInUse int32 req = req.WithContext(context.WithValue(req.Context(), silenceSemWarnContextKey, func() { atomic.StoreInt32(&allowQuerySemicolonsInUse, 1) })) defer func() { if atomic.LoadInt32(&allowQuerySemicolonsInUse) == 0 { sh.srv.logf("http: URL query contains semicolon, which is no longer a supported separator; parts of the query may be stripped when parsed; see golang.org/issue/25192") } }() } handler.ServeHTTP(rw, req) // 調(diào)用DefaultServeMux的ServeHTTP }
最后一行調(diào)用DefaultServeMux的ServeHTTP,上文已介紹過(guò),它的作用就是獲取到請(qǐng)求對(duì)應(yīng)的處理器函數(shù)并執(zhí)行。
延伸
gin框架是基于http包封裝的,gin匹配路由的方式不同于原生包,使用前綴路由樹來(lái)匹配路由,通過(guò)engin.Run啟動(dòng)服務(wù),其內(nèi)部也是調(diào)用的http.ListenAndServe(),那gin是如何應(yīng)用的自定義匹配方式呢?其實(shí)很簡(jiǎn)單,上文提到,調(diào)用http.ListenAndServe()時(shí)第二個(gè)參數(shù)handler是nil的話,會(huì)使用DefaultServeMux當(dāng)做復(fù)用器,那engin.Run()中調(diào)用時(shí)傳入gin定義的復(fù)用器就好了。
func (engine *Engine) Run(addr ...string) (err error) { defer func() { debugPrintError(err) }() if engine.isUnsafeTrustedProxies() { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") } address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) err = http.ListenAndServe(address, engine.Handler()) // engin.Handler()返回gin的自定義處理器 return }
以上就是淺析Golang中的net/http路由注冊(cè)與請(qǐng)求處理的詳細(xì)內(nèi)容,更多關(guān)于go net/http的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解析GOROOT、GOPATH、Go-Modules-三者的關(guān)系
這篇文章主要介紹了解析GOROOT、GOPATH、Go-Modules-三者的關(guān)系,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10Go語(yǔ)言實(shí)現(xiàn)讀取文件的方式總結(jié)
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言實(shí)現(xiàn)讀取文件的幾個(gè)方式,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定的幫助,感興趣的小伙伴可以收藏一下2023-04-04Go實(shí)現(xiàn)分布式唯一ID的生成之雪花算法
本文主要介紹了Go實(shí)現(xiàn)分布式唯一ID的生成之雪花算法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05Go語(yǔ)言中for循環(huán)的經(jīng)典案例分析
for循環(huán)問(wèn)題,在面試中經(jīng)常都會(huì)被問(wèn)到,并且在實(shí)際業(yè)務(wù)項(xiàng)目中也經(jīng)常用到for循環(huán),要是沒(méi)用好,一不下心就掉坑。本文為大家挑選了幾個(gè)經(jīng)典的案例,一塊來(lái)探討下,看看如何避免掉坑,多積累積累采坑經(jīng)驗(yàn)2023-02-02一文帶你了解Go語(yǔ)言中鎖特性和實(shí)現(xiàn)
Go語(yǔ)言中的sync包主要提供的對(duì)并發(fā)操作的支持,標(biāo)志性的工具有cond(條件變量)?once?(原子性)?還有?鎖,本文會(huì)主要向大家介紹Go語(yǔ)言中鎖的特性和實(shí)現(xiàn),感興趣的可以了解下2024-03-03詳解Go?flag實(shí)現(xiàn)二級(jí)子命令的方法
這篇文章主要介紹了Go?flag?詳解,實(shí)現(xiàn)二級(jí)子命令,本文就探討一下?Go?語(yǔ)言中如何寫一個(gè)擁有類似特性的命令行程序,需要的朋友可以參考下2022-07-07Go html/template 模板的使用實(shí)例詳解
這篇文章主要介紹了Go html/template 模板的使用實(shí)例詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-05-05