golang的http分析


首先,要認識一個貫穿始終的接口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傳入。
 


免責聲明!

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



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