Go 語言實現的 Web 服務工作方式與其他形式下的 Web 工作方式並沒有什么不同,具體流程如下:
—— http包執行流程
Request:來自用戶的請求信息,包括 post、get、Cookie、url 等。
Response:服務器返回給客戶端的信息。
Connect:用戶的每次的請求連接
Handler:處理請求和生成返回信息的處理邏輯
根據上圖,Go 語言中的 http 包具體做了這么三個操作:
- 創建 Listen Socket,監聽指定端口,等待客戶端請求。
- Listen Socket 接受客戶端請求,得到 Client Socket,接下來通過 Client Socket與客戶端通信
- 處理客戶端請求。首先從 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處理連接流程