Go http包執行流程


Go 語言實現的 Web 服務工作方式與其他形式下的 Web 工作方式並沒有什么不同,具體流程如下:

—— http包執行流程

Request:來自用戶的請求信息,包括 post、get、Cookie、url 等。
Response:服務器返回給客戶端的信息。
Connect:用戶的每次的請求連接
Handler:處理請求和生成返回信息的處理邏輯

根據上圖,Go 語言中的 http 包具體做了這么三個操作:

  1. 創建 Listen Socket,監聽指定端口,等待客戶端請求。
  2. Listen Socket 接受客戶端請求,得到 Client Socket,接下來通過 Client Socket與客戶端通信
  3. 處理客戶端請求。首先從 Client Socket 獲取 HTTP 請求數據,然后交給相應的 handler 處理請求,handler 處理完畢后再通過 Client Socket 返回給客戶端。

接着我們從代碼的角度來看一下這三個操作是如何實現的:

一,監聽端口

在 Go 語言中只需要通過調用 ListenAndServe 方法即可設置監聽端口:

func main() {
	http.HandleFunc("/", sayhelloName)       //設置訪問的路由
	
	err := http.ListenAndServe(":9090", nil) //設置監聽的端口
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

func sayhelloName(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello Wrold!") //這個寫入到w的是輸出到客戶端的
}

運行結果如下:

我們接着看一下 ListenAndServe 方法的具體實現,看一下它是如何實現監聽端口的:

// ListenAndServe listens on the TCP network address addr and then calls
// Serve with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case the DefaultServeMux is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

通過查看該方法的源碼,可以發現該方法初始化了一個 server 對象,並調用了該 server 對象的 ListenAndServe() 方法:

// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// If srv.Addr is blank, ":http" is used.
//
// 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) //底層使用tcp協議搭建了一個服務,然后監聽所設置的端口
	if err != nil {
		return err
	}
	return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

在這個方法中調用了 net.Listen("tcp", addr) 來監聽端口

二,接收客戶端請求

在完成了對端口的監聽之后,再通過調用 srv.Serve(net.Listener) 方法來處理接收客戶端的請求消息。

// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each. The service goroutines read requests and
// then call srv.Handler to reply to them.
//
// HTTP/2 support is only enabled if the Listener returns *tls.Conn
// connections and they were configured with "h2" in the TLS
// Config.NextProtos.
//
// 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
		}

        ...
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
		go c.serve(ctx)
	}
}

在這個方法中,啟動了一個 for 循環使 Listener 不斷地接收來自客戶端的請求,並且對每一個請求都實例化一個 Conn,並開啟一個 goroutine 來為這個請求進行服務 go c.serve()。用戶的每一次請求都是在一個新的 goroutine中服務,互相不影響。

三,為不同的請求分配處理邏輯

對於來自客戶端的不同請求,服務器端需要根據情況來分配相對應的函數進行處理。
在之前的 main 方法中,我們調用了 http.ListenAndServe(":9090", nil) 來監聽端口,而第二個參數傳入的是 nil,那么這個參數是干嘛用的呢?

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

type Server struct {
    Addr    string      // 監聽的地址和端口 默認為":http"
    Handler Handler     // 路由管理,如果為nil,則默認為http.DefaultServeMux
    ReadTimeout time.Duration //讀的最大超時時間
    WriteTimeout time.Duration //寫的最大超時時間
    MaxHeaderBytes int         //請求頭的最大長度
    TLSConfig *tls.Config      // 配置TLS
    ...
}

從源碼中的注釋可以看出,當初始化 Server 時,如果不指定參數 Handler,則默認獲取 Handler = http.DefaultServeMux,而 DefaultServeMux 又是一個默認創建的 ServeMux 類型的數據。

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux

從源碼上看到,DefaultServeMux 又是一個默認創建的 ServeMux。那么這個 ServeMux 又是干嘛的呢?

ServeMux

ServeMux 是 Go 語言中默認的路由規則管理器,它維護了一個存放了路由信息的 map 表,key 是請求路徑,而 value 則是該路徑所對應的處理程序(Handler)。當有請求到來的時候,根據這個 map 路由表來判斷將請求分發個哪個 Handler。

type ServeMux struct {
    mu    sync.RWMutex //鎖機制,用於處理並發
    m     map[string]muxEntry //路由表
    hosts bool // 是否為主機模式
}
 
type muxEntry struct {
    h       Handler
    pattern string
}

通過查看這兩個結構體的源碼,就能更容易的理解路由是如何處理請求的了:
當接收到一個請求時,server 就會根據 ServeMux.m 中保存的請求路徑(string)來匹配相對應的 muxEntry,接着就可以調用 muxEntry 中 h Handler 的ServeHttp 方法來處理請求了。

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

回到最開始的例子 main 方法中,我們為根路徑 "/" 注冊了一個 sayhelloName 函數,但是 sayhelloName 函數並沒有實現 ServeHTTP 接口,那么為什么它能起到作用呢?這因為在 http 包內還定義了一個 HandleFunc 類型,這個類型默認就實現了 ServeHTTP 這個接口。

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

而我們注冊的自定義 sayhelloName 函數,經過 http.HandleFunc("/", sayhelloName) ,最后都會被轉換成 HandlerFunc 類型。

// 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)
}
...
// 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)) //轉換自定義的func為HandlerFunc類型
}

在上面這段代碼可以看到,因為調用了 HandlerFunc(handler) 方法進行了強制類型轉換,我們自定義的函數被都轉換為了 HandlerFunc 類型。而 HandlerFunc 又實現了 Handler 接口的 ServeHTTP 方法,所以 HandlerFunc 是 Handler 的具體實現類型,所以 mux 也就擁有了 ServeHTTP 方法了。

到此,路由器就注冊好了相應的路由規則了。我們接着來分析一下路由是如何分發處理邏輯的。

路由分發

路由器接收到請求之后就會調用 mux.handler(r).ServeHTTP(w, r),也就是調用相應路由的 handler 的 ServeHTTP 接口。

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

原來它是根據用戶請求的 URL 和路由器中的存儲的 map 去匹配的,當匹配到路徑之后,就會返回存儲的 handler,接着就可以調用這個 handler 的 ServeHTTP 執行相應的函數了。

路由匹配規則

// Find a handler on a handler map given a path string.
// Most-specific (longest) pattern wins.
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.
    var n = 0
    for k, v := range mux.m {
        if !pathMatch(k, path) {
            continue
        }
        if h == nil || len(k) > n {
            n = len(k)
            h = v.h
            pattern = v.pattern
        }
    }
    return
}
// Does path match pattern?
func pathMatch(pattern, path string) bool {
    if len(pattern) == 0 {
        // should not happen
        return false
    }
    n := len(pattern)
    if pattern[n-1] != '/' {
        return pattern == path
    }
    return len(path) >= n && path[0:n] == pattern
}

總結流程圖:

—— http處理連接流程


免責聲明!

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



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