本文是晚輩對net/http包的一點淺顯的理解,文中如有錯誤的地方請前輩們指出,以免誤導!
轉摘本文也請注明出處:Go語言備忘錄(3):net/http包的使用模式和源碼解析,多謝!
目錄:
Handler接口:所有請求的處理器、路由ServeMux都滿足該接口;
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
- http包有一個包級別變量DefaultServeMux,表示默認路由:var DefaultServeMux = NewServeMux(),使用包級別的http.Handle()、http.HandleFunc()方法注冊處理器時都是注冊到該路由中;
- ServeMux結構體有ServeHTTP()方法(滿足Handler接口),主要用於間接調用它所保存的處理器的ServeHTTP()方法
type HandlerFunc func(ResponseWriter, *Request) //實現Handler接口的ServeHTTP方法 func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) //調用自身 }
二、HTTP服務器的使用模式:
處理函數:只要函數的簽名為 func(w http.ResponseWriter, r *http.Request) ,均可作為處理函數,即它可以被轉換為http.HandlerFunc函數類型;
模式一:使用默認的路由來注冊處理函數:
var addr = flag.String("addr", ":8080", "http server address") //1.不帶參數處理函數 func serveHome(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } http.ServeFile(w, r, "home.html") } //2.帶參數處理函數,閉包函數隱式轉換為http.HandlerFunc函數類型 func myHandler(s string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } http.ServeFile(w, r, s) //使用參數s } } func main() { flag.Parse() //向默認路由注冊處理器函數 http.HandleFunc("/", serveHome) //或http.Handle("/", http.HandlerFunc(serveHome)) http.Handle("/file",myHandler("somefile")) err := http.ListenAndServe(*addr, nil) //啟動監聽,第二個參數nil表示使用默認路由DefaultServeMux中注冊的處理器 if err != nil { log.Fatalln("ListenAndServe: ", err) } }
模式二:使用自定義的路由來注冊處理函數:
func main() { mux := http.NewServeMux() //新建一個自定義的路由 mux.Handle("/file",myHandler("somefile")) mux.HandleFunc("/", serveHome) err := http.ListenAndServe(*addr,mux) //啟動監聽 if err != nil { log.Fatalln("ListenAndServe: ", err) } }
模式三:直接自定義一個Server實例:該模式可以很方便的管理服務端的行為
mux := http.NewServeMux() mux.Handle("/file",myHandler("somefile")) mux.HandleFunc("/", serveHome) s := &http.Server{ Addr: ":8080", Handler: mux, //指定路由或處理器,不指定時為nil,表示使用默認的路由DefaultServeMux ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, ConnState: //指定連接conn的狀態改變時的處理函數 //.... } log.Fatal(s.ListenAndServe())
接下來,我們就跟蹤源碼來仔細的分析下整個執行過程。
三、HTTP服務器的執行過程:
1.使用http.ListenAndServe()方法啟動服務,它根據給定參數構造Server類型,然后調用server.ListenAndServe()
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
2.而server.ListenAndServe()方法內部調用net.Listen("tcp", addr),該方法內部又調用net.ListenTCP()創建並返回一個監聽器net.Listener,如下的ln;
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)}) }
3.然后把監聽器 ln 斷言轉換為 TCPListener 類型,並根據它構造一個 tcpKeepAliveListener 對象並傳遞給server.Serve()方法;
- 因為TCPListener實現了Listener接口,所以tcpKeepAliveListener也實現了Listener接口,並且它重寫了Accept()方法,目的是為了調用SetKeepAlive(true),讓操作系統為收到的每一個連接啟動發送keepalive消息(心跳,為了保持連接不斷開)。
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 }
4.server.Serve()方法調用tcpKeepAliveListener 對象的 Accept() 方法返回一個連接conn(該連接啟動了心跳),並為每一個conn創建一個新的go程執行conn.server()方法:具體見代碼中我加的注釋說明
func (srv *Server) Serve(l net.Listener) error { defer l.Close() if fn := testHookServerServe; fn != nil { fn(srv, l) } var tempDelay time.Duration //重試間隔 if err := srv.setupHTTP2_Serve(); err != nil { return err } srv.trackListener(l, true) //緩存該監聽器 defer srv.trackListener(l, false) //從緩存中刪除當前監聽器 baseCtx := context.Background() ctx := context.WithValue(baseCtx, ServerContextKey, srv) //新建一個context用來管理每個連接conn的Go程 for { rw, e := l.Accept() //調用tcpKeepAliveListener對象的 Accept() 方法 if e != nil { select { case <-srv.getDoneChan(): return ErrServerClosed //退出Serve方法,並執行延遲調用(從緩存中刪除當前監聽器) default: } //如果發生了net.Error錯誤,則隔一段時間就重試一次,間隔時間每次翻倍,最大為1秒 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) //該方法根據net.Conn、srv構造了一個新的http.conn類型 c.setState(c.rwc, StateNew) //緩存該連接的狀態,如果方法:Server.ConnState(net.Conn, ConnState)不為nil,就根據當前連接的狀態執行它 go c.serve(ctx) } }
5.而conn.server(
)方法會讀取請求,然后根據conn內保存的server來構造一個serverHandler類型,並調用它的ServeHTTP()方法:serverHandler{c.server}.ServeHTTP(w, w.req),該方法的源碼如下:
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) }
6.如上源碼可以看到,當 handler == nil 時使用默認的DefaultServeMux路由,否則使用在第1步中為Serve指定了的Handler;然后調用該Handler的ServeHTTP方法(該Handler一般被設置為路由ServeMux類型);
7.而路由ServeMux的ServeHTTP方法則會根據當前請求提供的信息來查找最匹配的Handler(這里為):
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) //規范化請求的路徑格式,查找最匹配的Handler h.ServeHTTP(w, r) }
8.以上查找到的Handler接口值h就是我們事先注冊到路由中與請求匹配的Handler;而h的動態類型是HandlerFunc類型(它也滿足Handler接口);
所以,以上 h.ServeHTTP(w, r) 實際上調用的是接口值h中持有的動態值(也就是我們定義的處理函數)
type HandlerFunc func(ResponseWriter, *Request) //實現Handler接口的ServeHTTP方法 func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) //調用自身 }
至此,整個調用過程講解完畢,至於業務層的處理邏輯,則由各個處理函數實現
func main() { mux := http.NewServeMux() mux.Handle("/to",http.RedirectHandler("http://example.org", 307)) err := http.ListenAndServe(*addr,mux) //啟動監聽 if err != nil { log.Fatalln("ListenAndServe: ", err) } }
好了,本文就暫時講關於http包關於HTTP服務端方面的東西,至於客戶端方面的就簡單引用一下官方文檔說明吧,畢竟客戶端很少用Go實現。
五、客戶端的實現:
Get、Head、Post和PostForm函數發出HTTP/ HTTPS請求。
resp, err := http.Get("http://example.com/") ... resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf) ... resp, err := http.PostForm("http://example.com/form", url.Values{"key": {"Value"}, "id": {"123"}})
程序在使用完回復后必須關閉回復的主體。
resp, err := http.Get("http://example.com/") if err != nil { // handle error } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) // ...
要管理HTTP客戶端的頭域、重定向策略和其他設置,創建一個Client:
client := &http.Client{ CheckRedirect: redirectPolicyFunc, } resp, err := client.Get("http://example.com") // ... req, err := http.NewRequest("GET", "http://example.com", nil) // ... req.Header.Add("If-None-Match", `W/"wyzzy"`) resp, err := client.Do(req) // ...
要管理代理、TLS配置、keep-alive、壓縮和其他設置,創建一個Transport:
tr := &http.Transport{ TLSClientConfig: &tls.Config{RootCAs: pool}, DisableCompression: true, } client := &http.Client{Transport: tr} resp, err := client.Get("https://example.com")
Client和Transport類型都可以安全的被多個go程同時使用。出於效率考慮,應該一次建立、盡量重用。
以上如有誤導的地方,請前輩們務必指出!