淺析Golang中的net/http路由注冊與請求處理
注冊路由
初學(xué)web時(shí),我們常用http.HandleFunc()來注冊路由,然后用http.ListenAndServe()來啟動服務(wù),接下來讓我們分析一下http包內(nèi)部是如何注冊路由的。
除了常用的http.HandleFunc()可以注冊路由,還有http.Handle可以注冊,先看一下源碼。
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) } func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }
對比一下兩個(gè)函數(shù)的不同:
1、分別調(diào)用了DefaultServeMux的Handle和HandleFunc方法。
2、handler參數(shù)類型分別為http.Handler接口,和func(ResponseWriter, *Request)類型。說明一下,DefaultServerMux是http包的全局變量,如果不使用默認(rèn)的復(fù)用器
接下來看一下Handle和HandleFunc的主要源碼,和Handler接口
// 注冊路由 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注冊路由 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)了路由的注冊,mux.m記錄了路由與處理器的映射;而ServeMux.HandleFunc將handler參數(shù)轉(zhuǎn)換成了HandlerFunc類型,然后調(diào)用ServeMux.Handle。
那么問題來了,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ù)簽名是一致的,如果沒有HandlerFunc,要注冊函數(shù)的話,我們就要自己定義結(jié)構(gòu)體,寫ServeHTTP方法,實(shí)現(xiàn)Handler接口,而有了HandlerFunc我們就可以把這一步省去了,在設(shè)計(jì)模式中,這叫裝飾器模式。
處理請求
ServerMux
使用http.HandleFunc和http.Handle注冊的路由都注冊到了DefaultServerMux,它也實(shí)現(xiàn)了handler接口,那讓我們來看一下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()中會調(diào)用mux.match(),從ServeMux.m(map[string]muxEntry類型),獲取路由和對應(yīng)處理器函數(shù)(我們傳入的),然后就可以調(diào)用h.ServeHTTP(w,r)來處理對應(yīng)的請求。
現(xiàn)在已得知我們傳入的處理函數(shù)是被ServeMux.ServeHTTP()調(diào)用,那ServerMus.ServeHTTP()又是怎么被調(diào)用的呢?接下來跟蹤一下http.ListenAndServe()的源碼。
Server
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
用http.ListenAndServe()啟動服務(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)來接收連接,并為每一個(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 // 通過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,上文已介紹過,它的作用就是獲取到請求對應(yīng)的處理器函數(shù)并執(zhí)行。
延伸
gin框架是基于http包封裝的,gin匹配路由的方式不同于原生包,使用前綴路由樹來匹配路由,通過engin.Run啟動服務(wù),其內(nèi)部也是調(diào)用的http.ListenAndServe(),那gin是如何應(yīng)用的自定義匹配方式呢?其實(shí)很簡單,上文提到,調(diào)用http.ListenAndServe()時(shí)第二個(gè)參數(shù)handler是nil的話,會使用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路由注冊與請求處理的詳細(xì)內(nèi)容,更多關(guān)于go net/http的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解析GOROOT、GOPATH、Go-Modules-三者的關(guān)系
這篇文章主要介紹了解析GOROOT、GOPATH、Go-Modules-三者的關(guān)系,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10Go語言實(shí)現(xiàn)讀取文件的方式總結(jié)
這篇文章主要為大家詳細(xì)介紹了Go語言實(shí)現(xiàn)讀取文件的幾個(gè)方式,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Go語言有一定的幫助,感興趣的小伙伴可以收藏一下2023-04-04Go實(shí)現(xiàn)分布式唯一ID的生成之雪花算法
本文主要介紹了Go實(shí)現(xiàn)分布式唯一ID的生成之雪花算法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05詳解Go?flag實(shí)現(xiàn)二級子命令的方法
這篇文章主要介紹了Go?flag?詳解,實(shí)現(xiàn)二級子命令,本文就探討一下?Go?語言中如何寫一個(gè)擁有類似特性的命令行程序,需要的朋友可以參考下2022-07-07Go html/template 模板的使用實(shí)例詳解
這篇文章主要介紹了Go html/template 模板的使用實(shí)例詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-05-05