多讀go的源碼,能夠加深對go語言的理解和認知,今天分享一下http相關的源碼部分
在不使用第三方庫的狀況下,咱們能夠很容易的的用go實現一個http服務,html
package main import ( "fmt" "net/http" ) func IndexHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "hello world ! ") } func main() { http.HandleFunc("/", IndexHandler) if err := http.ListenAndServe(":9100", nil); err != nil { panic(err) } }
直接在瀏覽器裏訪問9100端口就能夠返回 hello world !
go已經把全部的細節封裝好了,咱們只須要本身去寫Handler實現就夠了。源碼簡單來講作了如下幾件事:瀏覽器
DefaultServeMux
的Map裏好比:http.HandleFunc("/", IndexHandler)
(btw: go語言的map是非線程安全的,能夠在http源碼裏看到官方的處理方式);DefaultServeMux
根據request的path找到相應的Handler,把 request和 responseWriter傳給Handler 進行業務邏輯處理,response響應信息write給客戶端;http 包的默認路由 DefaultServeMux
是 ServeMux
結構休的實例
http.HandleFunc("/", IndexHandler)
的調用,會把path信息和自定義的方法信息保存到 DefaultServeMux
的 m map[string]muxEntry
變量裏
咱們看一下ServeMux
的定義:安全
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry es []muxEntry // slice of entries sorted from longest to shortest. hosts bool // whether any patterns contain hostnames } type muxEntry struct { h Handler pattern string }
ServeMux
中保存了path
和Handler
的對應關係,也是路由關係。服務器
muxEntry
中的 h Handler
對就的就是咱們自定義的Handler方法好比,咱們本身例子中的方法 func IndexHandler(w http.ResponseWriter, r *http.Request)
細心的同窗可能會問 Handler是一個接口,可是咱們只是定義了一個方法,這是怎麼轉換的呢?
接口Halder設置了簽名規則,也就是咱們自定義的處理方法多線程
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
go語言中全部的自定義類型均可以實現本身的方法,http包是用一個自定義的func來去實現了Handler接口,框架
type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
而後在ServerMux
的方法HandleFunc
處理的時候會把 handler func(ResponseWriter, *Request)
轉換成 HandlerFunc
, 以下所示:tcp
// HandleFunc registers the handler function for the given pattern. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { if handler == nil { panic("http: nil handler") } mux.Handle(pattern, HandlerFunc(handler)) }
ServerMux
結構中還有一個讀寫鎖 mu sync.RWMutex
mu就是用來處理多線程下map的安全訪問的。函數
獲得自定義的handler方法,就是去map中根據path匹配獲得Handler微服務
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) { mux.mu.RLock() defer mux.mu.RUnlock() // Host-specific pattern takes precedence over generic ones if mux.hosts { h, pattern = mux.match(host + path) } if h == nil { h, pattern = mux.match(path) } if h == nil { h, pattern = NotFoundHandler(), "" } return } func (mux *ServeMux) match(path string) (h Handler, pattern string) { // Check for exact match first. v, ok := mux.m[path] if ok { return v.h, v.pattern } // Check for longest valid match. mux.es contains all patterns // that end in / sorted from longest to shortest. for _, e := range mux.es { if strings.HasPrefix(path, e.pattern) { return e.h, e.pattern } } return nil, "" }
ServeMux
實現了 Handler
接口,也是默認的路由調用的具體規則實現的地方,他的 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) h.ServeHTTP(w, r) }
接口Halder設置了簽名規則,也就是咱們自定義的處理方法
好比下面的代碼,函數IndexHandler就是咱們自定義的方法,返回給客戶端請求一個 hello world !
字符串。中間請求是如何調用到咱們自定義的方法的具體邏輯都是http包提供的,可是一點也不神祕,
http.HandleFunc("/", IndexHandler) // IndexHandler 咱們本身定義的Handler方法 func IndexHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "hello world ! ") }
type Handler interface { ServeHTTP(ResponseWriter, *Request) } // type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
說完 ServeMux
是如何結合 Handler
接口,來實現路由和調用後,就要說一下,http服務是如何獲得客戶端傳入的信息,封裝requet和rresponse的。
在啓動程序的時候http.ListenAndServe
, 有兩個參數,第一個是指寫端口號,第二個是處理邏輯,若是咱們沒有給定處理邏輯,會使用默認的處理DefaultServeMux
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) }
ListenAndServe
方法打開tcp端口進行監聽,而後把Listener
傳給srv.Serve
方法
func (srv *Server) ListenAndServe() error { // 省略部分代碼 ... ln, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) }
具體要說一下 Service
方法,這個方法中,對監聽tcp請求,而後把請求的客戶端鏈接進行封裝,
func (srv *Server) Serve(l net.Listener) error { // 省略部分代碼 ... ctx := context.WithValue(baseCtx, ServerContextKey, srv) for { rw, e := l.Accept() // 省略部分代碼 ... tempDelay = 0 c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return go c.serve(ctx) } }
把客戶端的請求封裝成一個Conn,而後啓動一個協程go c.serve(ctx)來處理這個鏈接請求,這就是http包快的一個重要緣由
,每個鏈接就是一個協程。客戶端能夠先和服務器進行鏈接,而後利用這個conn來屢次發送http請求,這樣,就能夠減小每次的進行鏈接而提升一些速度。像一些rpc裏就是利用這點去實現的雙向的stream流,好比我以前的帖子go微服務框架go-micro深度學習(五) stream 調用過程詳解,他就是創建一個tcp鏈接,而後基於這個conn,發送多個request,返回屢次respose數據。
// Serve a new connection. func (c *conn) serve(ctx context.Context) { c.remoteAddr = c.rwc.RemoteAddr().String() ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr()) // 省略部分代碼 ... // 循環讀取請求 ... for { // 讀取請求數據,封裝response w, err := c.readRequest(ctx) if c.r.remain != c.server.initialReadLimitSize() { // If we read any bytes off the wire, we're active. c.setState(c.rwc, StateActive) } // 省略部分代碼 ... // 路由調用自定義的方法,把封裝好的responseWrite和 request傳到方法內 serverHandler{c.server}.ServeHTTP(w, w.req) w.cancelCtx() if c.hijacked() { return } w.finishRequest() // 省略部分代碼 ... } }