在Go語言的http包中有兩個核心的功能:Conn,ServeMux。
Conn的goroutine
之前我們說過使用者的每一次請求Conn都是在一個新的goroutine去處理,相互不影響,不會被阻塞,這就是Go的高並發的體現。在 func (srv *Server) Serve(l net.Listener) error 函數中有這樣的描述
Serve accepts incoming connections on the Listener l, creating a new service goroutine for each.
具體代碼如下:
c := srv.newConn(rw)c.setState(c.rwc, StateNew) // before Serve can returngo c.serve(ctx) //傳遞到對應的handle
用戶端的每次請求都會建立一個Conn,這個Conn裡面儲存了該次請求的資訊,然後再傳遞到對應的handler,該handler中便可以讀取到相應的header資訊,這樣保證了每個請求的獨立性。
ServeMux的自訂
在 ServeHTTP 函數中若我們沒有指定handler(即http.ListenAndServe的第二個參數為nil),系統就會指定預設的handler:DefaultServeMux,通過路由器把本次請求的資訊傳遞到了後端的處理函數。那麼這個路由器是怎麼實現的呢?下面是我們沒有指定路由器時,系統自動設定預設的路由器代碼。
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { handler := sh.srv.Handler if handler == nil { handler = DefaultServeMux //指定預設的 handler路由器 } if req.RequestURI == "*" && req.Method == "OPTIONS" { handler = globalOptionsHandler{} } handler.ServeHTTP(rw, req)}
關於 ServeMux 的解釋與結構如下所示
ServeMux是一個HTTP請求多工器。它將每個傳入請求的URL與登入模式的列表進行匹配,並調用與URL最匹配的模式的處理常式。模式名稱是固定的,有根的路徑,如“/favicon.ico”,或帶根的子樹,如“/ images /”(請注意尾部斜杠)。較長的模式優先於較短的模式,因此如果有“/ images /”和“/ images / thumbnails /”註冊的處理常式,則後面的處理常式將被調用以“/ images / thumbnails /”開頭的路徑和前者將收到“/ images /”子樹中任何其他路徑的請求。請注意,由於以斜杠結尾的模式命名為有根的子樹,因此模式“/”匹配所有未與其他登入模式比對的路徑,而不僅僅是具有Path ==“/”的URL。如果登入子樹並且收到命名子樹根但沒有其斜杠的請求,則ServeMux會將該請求重新導向到子樹根(添加尾部斜杠)。可以通過單獨註冊路徑而不使用尾部斜杠來覆蓋此行為。例如,註冊“/ images /”會導致ServeMux將“/ images”請求重新導向到“/ images /”,除非“/ images”已單獨註冊。模式可以選擇以主機名稱開頭,僅限制與該主機上的URL匹配。特定於主機的模式優先於一般模式,因此處理常式可以註冊兩個模式“/ codesearch”和“[codesearch.google.com/](http://codesearch.google.com/)”,而無需接管“[http://www.google.com/](http://www.google.com/)”的請求”。ServeMux還負責清理URL請求路徑,重新導向包含的任何請求。或..元素或重複斜杠到等效的,更清晰的URL。type ServeMux struct { mu sync.RWMutex // 鎖,由於請求涉及到並發處理,因此這裡需要一個鎖機制 m map[string]muxEntry // 路由規則,一個 string 對應一個 mux 實體,這裡的 string 就是註冊的路由 hosts bool // 是否有任何模式包含主機名稱}
muxEntry結構如下所示
type muxEntry struct { h Handler // 這個路由運算式對應哪個 handler pattern string // 模式}
接下來看看Handler的解釋和定義
處理常式響應HTTP請求。ServeHTTP應該將回複標題和資料寫入ResponseWriter,然後返回。返回請求完成的訊號;在完成ServeHTTP調用之後或同時使用ResponseWriter或從Request.Body讀取是無效的。根據HTTP用戶端軟體,HTTP協議版本以及用戶端和Go伺服器之間的任何中介,可能無法在寫入ResponseWriter後從Request.Body讀取。謹慎的處理常式應首先閱讀Request.Body,然後回複。除了閱讀本文外,處理常式不應修改提供的請求。如果ServeHTTP發生panic,伺服器(ServeHTTP的調用者)假定panic的影響與活動請求隔離。它恢複了panic,將堆疊追蹤記錄到伺服器錯誤日誌,並關閉網路連接或發送HTTP / 2 RST_STREAM,具體取決於HTTP協議。要中止處理常式以便用戶端看到響應中斷但伺服器不記錄錯誤,請使用值ErrAbortHandler進行panic。type Handler interface { ServeHTTP(ResponseWriter, *Request) //路由實現器}
在我們之前寫的Web程式中,並沒有實現 Handler 介面的 ServeHTTP 方法,那我們為什麼還可以使用該方法來訪問路由呢?因為我們使用了 http.HandleFunc 函數。
我們來看看這個函數的具體實現
//HandleFunc在DefaultServeMux中註冊給定模式的處理函數。func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler)}
這裡 DefaultServeMux.HandleFunc 調用的是下面這個函數
// HandleFunc為給定模式註冊處理函數。func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { mux.Handle(pattern, HandlerFunc(handler)) }
上面的HandlerFunc(handler) 調用的是以下的代碼,我們調用了HandlerFunc(f),強制類型轉換f成為HandlerFunc類型,這樣f就擁有了ServHTTP方法。
//HandlerFunc類型是一個允許將普通函數用作HTTP處理常式的適配器。 如果f是具有適當簽名的函數,則HandlerFunc(f)是一個調用f的Handler。type HandlerFunc func(ResponseWriter, *Request)// ServeHTTP calls f(w, r).func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) //調用 HandlerFunc}
另外 mux.Handle調用如下函數,根據給定的模式註冊對應的handler和路由規則,若以存在此規則就會panic
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 }}
經過上面的流程,我們就通過 http.HandleFunc("/",myGoWebService) 在設定了ServeHTTP中儲存好了相應的路由規則。
路由器裡面儲存好了相應的路由規則之後,那麼伺服器接收到具體的請求後又是怎麼根據url選擇對應的handler呢?
路由器接收到請求之後調用 mux.handler(r).ServeHTTP(w, r),根據規則分發的邏輯代碼就是在mux.handler(r)中實現,相關代碼如下:
//handler是Handler的主要實現。除了CONNECT方法之外,已知路徑是規範形式。func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) { mux.mu.RLock() defer mux.mu.RUnlock() // 特定於主機的模式優先於通用模式 if mux.hosts { h, pattern = mux.match(host + path) } if h == nil { h, pattern = mux.match(path) } if h == nil { h, pattern = NotFoundHandler(), "" } return}
在上面代碼中使用match函數,根據使用者請求的URL和路由器裡面儲存的map去匹配的,當匹配到之後返回儲存的handler,調用這個handler的ServHTTP介面就可以執行到相應的函數了。
自訂路由功能
那麼如果我們要自訂路由功能,那要怎麼做呢?我們可以自己定義一個Handler,實現它的ServeHTTP 方法
import ( "net/http" "fmt")func main() { mux := &MyMux{} http.ListenAndServe(":9090",mux)}type MyMux struct {}func (p *MyMux)ServeHTTP(rw http.ResponseWriter,r *http.Request){ if r.URL.Path == "/" { myGoWebService(rw,r) return } http.NotFound(rw,r) return}func myGoWebService(rw http.ResponseWriter,request *http.Request) { fmt.Fprintf(rw,"Hello GoLang")}
若我們輸入http://localhost:9090/,那麼瀏覽器就會輸出下面結果 Hello GoLang,若我們輸入的地址後面不是“/”,那麼就會出現404 page not found
總結:
當我們調用 http.HandleFunc("/",myGoWebService)時,底層實際上按照順序做了以下幾件事情
①調用了DefaultServerMux的HandleFunc
②調用了DefaultServerMux的Handle
③往DefaultServeMux的map[string]muxEntry中增加對應的handler和路由規則
當我們調用 http.ListenAndServe(":9090",nil) 時,底層實際上按照順序做了以下幾件事情
①初始化一個server對象
②調用Server的ListenAndServe()
③調用net.Listen("tcp", addr)監聽連接埠
④啟動一個for{}迴圈,在這個迴圈體內通過Listener接收請求
⑤對每個請求執行個體化一個Conn,並且開啟一個goroutine為這個請求進行服務go c.serve()
⑥讀取每個請求的內容w, err := c.readRequest()
⑦判斷handler是否為空白,如果沒有設定handler(這個例子就沒有設定handler),handler就設定為DefaultServeMux
⑧調用handler的ServeHttp:serverHandler{c.server}.ServeHTTP(w, w.req)
最後當用戶端發送請求,根據url選擇對應的handler,並且進入到這個handler的ServeHTTP :mux.handler(r).ServeHTTP(w, r)
參考書籍:《Go Web編程》