Go 跟隨源碼分析http包下HTTP服務器請求過程

go語言可以用幾行代碼搭建一個http服務器,例如:

package main

import (
	"fmt"
	"net/http"
)

func SayHello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello World!")
}

func main() {
	http.HandleFunc("/", SayHello)
	http.ListenAndServe(":8080", nil)
}

也可以自定義多路複用器和http.Server搭建一個http服務器:

package main

import (
	"fmt"
	"net/http"
)

func SayHello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello World!")
}


func main() {
    // 創建一個多路複用器
	mux := http.NewServeMux()
    // 綁定pattern和Handler函數
	mux.HandleFunc("/", SayHello)
    // 創建server 綁定多路複用器
    server := http.Server{
		Addr:              ":8080",
		Handler:           mux,
		TLSConfig:         nil,
		ReadTimeout:       0,
		ReadHeaderTimeout: 0,
		WriteTimeout:      0,
		IdleTimeout:       0,
		MaxHeaderBytes:    0,
		TLSNextProto:      nil,
		ConnState:         nil,
		ErrorLog:          nil,
		BaseContext:       nil,
		ConnContext:       nil,
	}
    // 開始監聽
	server.ListenAndServe()
}

搭建服務器的準備

從上面的代碼可以看到go語言的http服務器有三個必需對象:handler函數(SayHello)、多路複用器(mux)和httpServer(server)。

先來看看從handler函數到服務器啓動的過程,首先創建一個和http.HandlerFunc簽名相同的handler函數SayHello,也可以是實現http.Handler接口的類型的對象。然後創建一個多路複用器,mux := http.NewServeMux()。

再調用mux.HandleFunc("/", SayHello)函數綁定路由和handler函數的關係。mux.HandleFunc("/", SayHello)後面做了2件事。1,把函數SayHello轉成HandlerFunc類型;2,調用mux.Handle(pattern string, handler Handler)方法把"/"和SayHello存儲到mux內部的map對象和切片對象中。

mux.HandleFunc("/", SayHello)方法的源碼

// 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))
}

mux.Handle(pattern, HandlerFunc(handler))的源碼

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
	mux.mu.Lock()
	defer mux.mu.Unlock()

	if pattern == "" {
		panic("http: invalid pattern")
	}
	if handler == nil {
		panic("http: nil handler")
	}
	if _, exist := mux.m[pattern]; exist {
		panic("http: multiple registrations for " + pattern)
	}

	if mux.m == nil {
		mux.m = make(map[string]muxEntry)
	}
	e := muxEntry{h: handler, pattern: pattern}
	mux.m[pattern] = e
	if pattern[len(pattern)-1] == '/' {
		mux.es = appendSorted(mux.es, e)
	}

	if pattern[0] != '/' {
		mux.hosts = true
	}
}

 然後創建相當於套接字功能的server對象,初始化server的Handler屬性爲mux,最後調用server.ListenAndServe()就完成了http服務器的最簡單的搭建。

服務器處理請求的過程

server.ListenAndServe()方法創建了一個套接字對象,並調用server.Serve(l net.Listener) 方法監聽該套接字。

// ListenAndServe always returns a non-nil error. After Shutdown or Close,
// the returned error is ErrServerClosed.
func (srv *Server) ListenAndServe() error {
	if srv.shuttingDown() {
		return ErrServerClosed
	}
	addr := srv.Addr
	if addr == "" {
		addr = ":http"
	}
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	return srv.Serve(ln)
}

在server.Serve(l net.Listener)方法內部有個死循環一直監聽套接字,一旦有數據被接收就會創建一個連接c,並調用創建一個 serve(ctx context.Context)函數協程。

// Serve always returns a non-nil error and closes l.
// After Shutdown or Close, the returned error is ErrServerClosed.
func (srv *Server) Serve(l net.Listener) error {
	// 代碼太長  省略
	for {
		rw, e := l.Accept()
		if e != nil {
			select {
			case <-srv.getDoneChan():
				return ErrServerClosed
			default:
			}
			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
		}
		connCtx := ctx
		if cc := srv.ConnContext; cc != nil {
			connCtx = cc(connCtx, rw)
			if connCtx == nil {
				panic("ConnContext returned nil")
			}
		}
		tempDelay = 0
		c := srv.newConn(rw)
		c.setState(c.rwc, StateNew) // before Serve can return
		go c.serve(connCtx)
	}
}

在c.serve(ctx context.Context)方法中調用了serverHandler{c.server}.ServeHTTP(w, w.req),這裏把c.server即上面文章前面搭建服務器的準備中介紹的相當於套接字功能的server對象,serverHandler類型實現了Handler接口。

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
	    // 源碼太長  省略掉其他部分
		serverHandler{c.server}.ServeHTTP(w, w.req)
        // 源碼太長  省略掉其他部分

}

看一下serverHandler.ServeHTTP(w,w.req)方法源碼,handler := sh.srv.Handler,handler其實是server.Handler,還記得在創建server對象時初始化的Handler屬性值嗎?就是mux(多路複用器),這個方法最終調用的是mux.ServeHTTP(rw, 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)
}

在mux.ServeHTTP(rw, req)內部調用了mux.Handler(r)方法得到handler函數對象,並執行這個函數。

// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
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)
}

至此一個請求的基本過程就完成了,但還有兩點要解釋。1,mux.Handler(r)根據request請求返回handler函數時調用了mux.handler(host, r.URL.Path),在mux.handler(host, r.URL.Path)內部又調用了mux.match(path)方法,最終這個match(path string) (h Handler, pattern string)方法纔是根據請求url查詢handler函數的重點。

// If there is no registered handler that applies to the request,
// Handler returns a ``page not found'' handler and an empty pattern.
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {

   // 代碼太長  省略
	return mux.handler(host, r.URL.Path)
}

// handler is the main implementation of Handler.
// The path is known to be in canonical form, except for CONNECT methods.
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
}

match(path string) (h Handler, pattern string)方法是從多路複用器mux內部map對象和切片對象尋找相同的請求路徑或者長得像的請求路徑,並返回handler函數,這裏的請求路徑和handler函數就是上文調用mux.HandleFunc("/", SayHello)綁定的請求路徑和handler函數。

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, ""
}

2,由於上文中我使用的是mux.HandleFunc("/", SayHello)綁定請求路徑和handler函數,所以調用mux.Handler(r)返回的其實是一個http.HandlerFunc類型的函數,並且http.HandlerFunc也實現了Handler接口,可以看到http.HandlerFunc的ServeHTTP(w ResponseWriter, r *Request)方法實現的是調用自身,即綁定的handler函數。

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}

 參考一下思維導圖


附本文參考書籍:

參考文獻:https://github.com/ffhelicopter/Go42/blob/master/content/42_36_http.md

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章