Go語言:net/http包的使用模式和源碼解析


目錄:

 
一、http包的3個關鍵類型:
Handler接口:所有請求的處理器、路由ServeMux都滿足該接口;
1
2
3
type  Handler  interface  {
    ServeHTTP(ResponseWriter, *Request)
}
ServeMux結構體:HTTP請求的多路轉接器(路由),它負責將每一個接收到的請求的URL與一個注冊模式的列表進行匹配,並調用和URL最匹配的模式的處理器。它內部用一個map來保存所有處理器Handler
  • http包有一個包級別變量DefaultServeMux,表示默認路由:var DefaultServeMux = NewServeMux(),使用包級別的http.Handle()、http.HandleFunc()方法注冊處理器時都是注冊到該路由中;
  • ServeMux結構體有ServeHTTP()方法(滿足Handler接口),主要用於間接調用它所保存的處理器的ServeHTTP()方法
http.HandlerFunc函數類型:它滿足Handler接口
1
2
3
4
5
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函數類型;

模式一:使用默認的路由來注冊處理函數:
 
 
模式二:使用自定義的路由來注冊處理函數:
 
 
模式三:直接自定義一個Server實例:該模式可以很方便的管理服務端的行為
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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()
1
2
3
4
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;

1
2
3
4
5
6
7
8
9
10
11
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消息(心跳,為了保持連接不斷開)。
1
2
3
4
5
6
7
8
9
10
11
12
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()方法:具體見代碼中我加的注釋說明
 

  

5.而conn.server()方法會讀取請求,然后根據conn內保存的server來構造一個serverHandler類型,並調用它的ServeHTTP()方法:serverHandler{c.server}.ServeHTTP(w, w.req),該方法的源碼如下:
 
1
2
3
4
5
6
7
8
9
10
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(這里為):
1
2
3
4
5
6
7
8
9
10
11
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中持有的動態值(也就是我們定義的處理函數)
1
2
3
4
5
type  HandlerFunc  func (ResponseWriter, *Request)
//實現Handler接口的ServeHTTP方法
func  (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
     f(w, r)  //調用自身
}
 
至此,整個調用過程講解完畢,至於業務層的處理邏輯,則由各個處理函數實現

四、重定向:
http包自帶了幾個創建常用處理器的函數:FileServer,NotFoundHandler、RedirectHandler、StripPrefix、TimeoutHandler。
而RedirectHandler函數就是用來重定向的:它返回一個請求處理器,該處理器會對每個請求都使用狀態碼code重定向到網址url
1
2
3
4
5
6
7
8
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請求。

1
2
3
4
5
6
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" }})

  

程序在使用完回復后必須關閉回復的主體。

1
2
3
4
5
6
7
resp, err := http.Get( "http://example.com/" )
if  err != nil {
     // handle error
}
defer  resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
// ...

  

要管理HTTP客戶端的頭域、重定向策略和其他設置,創建一個Client:

 
         
1
2
3
4
5
6
7
8
9
10
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:

 
         
1
2
3
4
5
6
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程同時使用。出於效率考慮,應該一次建立、盡量重用。

參考:https://blog.csdn.net/weixin_34302561/article/details/89749438


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM