首先,要認識一個貫穿始終的接口http.Handler
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
其中,兩個參數,一個是表示響應的接口,另一個表示請求。具體方法先忽略:
type ResponseWriter interface { }
使用時,這個函數指這定地址和對應的handler
func ListenAndServe(addr string, handler Handler)
再看下http包內的一個重要函數,Handle,可見,傳入的是一個監聽的http path,第二個參數是上述的handler.
func Handle(pattern string, handler Handler)
看一下如何使用的:
使用接口形式的Handle + ListenAndServe
type ImpHandler struct {} func (h ImpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // 實現方法 w.Write([]byte("haha")) } func main() { http.Handle("/", ImpHandler{}) http.ListenAndServe(":12345", nil ) }
這里,http消息來了應該是在底層直接調用對應的ServeHTTP。具體是怎么調到的,一層層來看。
首先看下http.Handle做了什么。
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
可見,這個Handle函數底層封裝了一個對象,其實是對此對象DefaultServeMux進行調用。
這類型如下:
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry hosts bool // whether any patterns contain hostnames } type muxEntry struct { explicit bool h Handler pattern string }
可見,http的path和對應的處理handler的關系以muxEntry維護在這個默認的hash表m中。http.Handle傳入的兩個參數以hash形式保存在內部的全局變量DefaultServeMux中。
到此,只是在http業務層面上將相關信息保存下,最后在http請求來時的ListenAndServe中,才進行連接的處理。
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
同樣,ListenAndServe本身只是一個對外接口,內部也有相應對象Server進行封裝。前面說過這個方法是處理連接層面的事,那么這個server就是tcp server的一個抽象。
另一方面,這里又傳入了一個handler,這是干嗎用的?這里傳的是nil,后面再看。
func (srv *Server) ListenAndServe() error { addr := srv.Addr if addr == "" { addr = ":http" } ln, err := net.Listen("tcp", addr) // 創建監聽了 if err != nil { return err } return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) }
可見,這里直接就監聽TCP連接了。其中的ln是個Listener接口,代碼這樣寫比較漂亮:
// Multiple goroutines may invoke methods on a Listener simultaneously. type Listener interface { // Accept waits for and returns the next connection to the listener. Accept() (Conn, error) // Close closes the listener. // Any blocked Accept operations will be unblocked and return errors. Close() error // Addr returns the listener's network address. Addr() Addr } // 這里實現得比較好,覆蓋了一個Accept方法,在其中加入了keepAlived的選項。其他兩個方法仍舊使用原listener的 type tcpKeepAliveListener struct { *net.TCPListener // 外層可直接調它的方法不需要指定成員 } func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { tc, err := ln.AcceptTCP() if err != nil { return } tc.SetKeepAlive(true) tc.SetKeepAlivePeriod(3 * time.Minute) return tc, nil }
繼續看Server的連接監聽處理:
func (srv *Server) Serve(l net.Listener) error { defer l.Close() if fn := testHookServerServe; fn != nil { fn(srv, l) } var tempDelay time.Duration // how long to sleep on accept failure if err := srv.setupHTTP2_Serve(); err != nil { return err } ////////////////skip for { rw, e := l.Accept() // 取出一個連接,對應accept if e != nil { 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) } }
可見,調用Listener的Accept()后,形成一個抽象的連接,再啟單獨協程去處理它。
協程內讀出對應的數據后,會進行如下調用,此調用將http的業務與底層的tcp連接結合了起來:
serverHandler{c.server}.ServeHTTP(w, w.req)
看下面,最終回調回去了。
// serverHandler delegates to either the server's Handler or // DefaultServeMux and also handles "OPTIONS *" requests. type serverHandler struct { srv *Server } 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) } // 最終回到最開始注冊Handle的地方,進行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) }
最終調用到了上文的DefaultServeMux中來。
以上是http一的基礎的結構,下面是一些衍生的用法。
使用HandleFunc + ListenAndServe
func main() { fmt.Println("Hello.") http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) { w.Write([]byte("haha2")) }) http.ListenAndServe(":12346", nil ) }
其中,func可使用閉包也可不用。
看下面代碼:
// HandleFunc registers the handler function for the given pattern. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { mux.Handle(pattern, HandlerFunc(handler)) } // HandleFunc registers the handler function for the given pattern // in the DefaultServeMux. // The documentation for ServeMux explains how patterns are matched. func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }
可見,HandlerFunc只是對Handler的封裝,下面同樣是通過DefaultServeMux來進行。
這里的重點是以下的寫法,用一個函數來實現某個接口,雖然這接口底層仍然是調用函數本身,這樣就可以直接用函數和之前的接口匹配:
// The HandlerFunc type is an adapter to allow the use of // ordinary functions as HTTP handlers. If f is a function // with the appropriate signature, HandlerFunc(f) is a // Handler that calls f. type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
實際上的效果是,明明只寫了一個函數func(ResponseWriter, *Request),但其他代碼卻可以通過golang的隱式接口方式通過另一個你不知道的函數調用你!這里,不知道的函數就是ServeHTTP。
Handle掌握了,這里的HandleFunc就容易了。
更進一步,
ServeMux也是可以使用自定義的值。這時,傳入http.ListenAndServe的第二個參數就是這個mux。
func NewServeMux() *ServeMux { return new(ServeMux) }
這個ServeMux,本身又是隱式實現了Handler。
再次回到這里,可見最終是調到了ServerMux這里:
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) }
總結下:
http包給外面提供了三個層次的接口,每個層次暴露的東西不一樣:
第一層: 只需要關心處理邏輯,直接以HandleFunc實現;
第二層: 以Handle實現,這一層,對外額外暴露了一個Handler接口,需要用戶關注一個ServeHTTP的函數;底層仍然是通過DefaultMux來實現。
第三層: 對外暴露了一個ServeMux,處理請求的方法注冊到這個ServeMux上,將ServeMux傳入。