Files
build-web-application-with-…/zh-tw/03.4.md
2019-06-22 23:41:28 +08:00

211 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 3.4 Go 的 http 套件詳解
前面小節介紹了 Go 怎麼樣實現了 Web 工作模式的一個流程,這一小節,我們將詳細地解剖一下 http 套件,看它到底是怎樣實現整個過程的。
Go 的 http 有兩個核心功能Conn、ServeMux
## Conn 的 goroutine
與我們一般編寫的 http 伺服器不同, Go 為了實現高併發和高效能, 使用了 goroutines 來處理 Conn 的讀寫事件, 這樣每個請求都能保持獨立,相互不會阻塞,可以高效的回應網路事件。這是 Go 高效的保證。
Go 在等待客戶端請求裡面是這樣寫的:
```Go
c, err := srv.newConn(rw)
if err != nil {
continue
}
go c.serve()
```
這裡我們可以看到客戶端的每次請求都會建立一個 Conn這個 Conn 裡面儲存了該次請求的資訊,然後再傳遞到對應的 handler該 handler 中便可以讀取到相應的 header 資訊,這樣保證了每個請求的獨立性。
## ServeMux 的自訂
我們前面小節講述 conn.server 的時候,其實內部是呼叫了 http 套件預設的路由器,透過路由器把本次請求的資訊傳遞到了後端的處理函式。那麼這個路由器是怎麼實現的呢?
它的結構如下:
```Go
type ServeMux struct {
mu sync.RWMutex //鎖,由於請求涉及到併發處理,因此這裡需要一個鎖機制
m map[string]muxEntry // 路由規則,一個 string 對應一個 mux 實體,這裡的 string 就是註冊的路由表示式
hosts bool // 是否在任意的規則中帶有 host 資訊
}
```
下面看一下 muxEntry
```Go
type muxEntry struct {
explicit bool // 是否精確匹配
h Handler // 這個路由表示式對應哪個 handler
pattern string //匹配字串
}
```
接著看一下 Handler 的定義
```Go
type Handler interface {
ServeHTTP(ResponseWriter, *Request) // 路由實現器
}
```
Handler 是一個介面,但是前一小節中的 `sayhelloName` 函式並沒有實現 ServeHTTP 這個介面,為什麼能新增呢?原來在 http 套件裡面還定義了一個型別`HandlerFunc`,我們定義的函式 `sayhelloName` 就是這個 HandlerFunc 呼叫之後的結果,這個型別預設就實現了 ServeHTTP 這個介面,即我們呼叫了 HandlerFunc(f),強制型別轉換 f 成為 HandlerFunc 型別,這樣 f 就擁有了 ServeHTTP 方法。
```Go
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
```
路由器裡面儲存好了相應的路由規則之後,那麼具體的請求又是怎麼分發的呢?請看下面的程式碼,預設的路由器實現了`ServeHTTP`
```Go
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
w.Header().Set("Connection", "close")
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
```
如上所示路由器接收到請求之後,如果是`*`那麼關閉連結,不然呼叫`mux.Handler(r)`回傳對應設定路由的處理 Handler然後執行`h.ServeHTTP(w, r)`
也就是呼叫對應路由的 handler 的 ServerHTTP 介面,那麼 mux.Handler(r)怎麼處理的呢?
```Go
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
if r.Method != "CONNECT" {
if p := cleanPath(r.URL.Path); p != r.URL.Path {
_, pattern = mux.handler(r.Host, p)
return RedirectHandler(p, StatusMovedPermanently), pattern
}
}
return mux.handler(r.Host, r.URL.Path)
}
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 介面就可以執行到相應的函數了。
透過上面這個介紹我們了解了整個路由過程Go 其實支援外部實現的路由器 `ListenAndServe`的第二個參數就是用以配置外部路由器的,它是一個 Handler 介面,即外部路由器只要實現了 Handler 介面就可以,我們可以在自己實現的路由器的 ServeHTTP 裡面實現自訂路由功能。
如下程式碼所示,我們自己實現了一個簡易的路由器
```Go
package main
import (
"fmt"
"net/http"
)
type MyMux struct {
}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
sayhelloName(w, r)
return
}
http.NotFound(w, r)
return
}
func sayhelloName(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello myroute!")
}
func main() {
mux := &MyMux{}
http.ListenAndServe(":9090", mux)
}
```
## Go 程式碼的執行流程
透過對 http 套件的分析之後,現在讓我們來梳理一下整個的程式碼執行過程。
- 首先呼叫 Http.HandleFunc
按順序做了幾件事:
1 呼叫了 DefaultServeMux 的 HandleFunc
2 呼叫了 DefaultServeMux 的 Handle
3 往 DefaultServeMux 的 map[string]muxEntry 中增加對應的 handler 和路由規則
- 其次呼叫 http.ListenAndServe(":9090", nil)
按順序做了幾件事情:
1 實體化 Server
2 呼叫 Server 的 ListenAndServe()
3 呼叫 net.Listen("tcp", addr)監聽埠
4 啟動一個 for 迴圈,在迴圈體中 Accept 請求
5 對每個請求實體化一個 Conn並且開啟一個 goroutine 為這個請求進行服務 go c.serve()
6 讀取每個請求的內容 w, err := c.readRequest()
7 判斷 handler 是否為空,如果沒有設定 handler這個例子就沒有設定 handlerhandler 就設定為 DefaultServeMux
8 呼叫 handler 的 ServeHttp
9 在這個例子中,下面就進入到 DefaultServeMux.ServeHttp
10 根據 request 選擇 handler並且進入到這個 handler 的 ServeHTTP
mux.handler(r).ServeHTTP(w, r)
11 選擇 handler
A 判斷是否有路由能滿足這個 request迴圈遍歷 ServeMux 的 muxEntry
B 如果有路由滿足,呼叫這個路由 handler 的 ServeHTTP
C 如果沒有路由滿足,呼叫 NotFoundHandler 的 ServeHTTP
## links
* [目錄](<preface.md>)
* 上一節:[Go 如何使得 web 工作](<03.3.md>)
* 下一節:[小結](<03.5.md>)