在 Go 語言中并闲,創(chuàng)建一個 HTTP 服務(wù)很簡單,只需要幾行代碼就可以創(chuàng)建一個可用的 HTTP 服務(wù)谷羞,這是因?yàn)?Go 原生幫我們實(shí)現(xiàn)了一個默認(rèn)的 HTTP 服務(wù)帝火,就是 ServeMux,在這篇文章中湃缎,我們來詳細(xì)看一下 ServeMux 的具體實(shí)現(xiàn)犀填。
1. 創(chuàng)建一個 HTTP 服務(wù)
在 Go 語言中,創(chuàng)建一個 HTTP 服務(wù)只需要寫下面幾行代碼就可以了嗓违。
func main() {
http.HandleFunc("/index", func(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("Hello go web"))
})
http.ListenAndServe(":8080", nil)
}
我們定義了一個路由九巡,然后啟動 HTTP 服務(wù),就可以在瀏覽器中通過 http://127.0.0.1:8080/index 來訪問服務(wù)靠瞎,服務(wù)端會返回 Hello go web
比庄。
這樣,一個簡單的 HTTP 服務(wù)就創(chuàng)建完成了乏盐。
2. HTTP 服務(wù)如何運(yùn)行
上面的代碼有兩部分佳窑,一部分是定義 HTTP 服務(wù)的路由,在服務(wù)啟動之后父能,我們訪問相應(yīng)的路由神凑,就能得到服務(wù)端的響應(yīng)。
在 http 包中,有一個接口 http.Handler
溉委,這個接口是 HTTP 服務(wù)的核心:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
對于 HTTP 服務(wù)來說鹃唯,一定發(fā)起請求的客服端和處理請求的服務(wù)端,客戶端發(fā)起一個請求瓣喊,然后服務(wù)端給出相應(yīng)的輸出坡慌。這個 Handler 接口就把這個整個過程抽象為 ServeHTTP 方法。ResponseWriter 表示服務(wù)端的輸出藻三,Request 表示來自客戶端的請求洪橘。
在 http 包中提供了一個 Handler 的實(shí)現(xiàn) ServeMux,這個Handler 做的事情也很簡單棵帽,就是來維護(hù) URL 和 Handler 之間的關(guān)系熄求,根據(jù) URL 判斷應(yīng)該把請求轉(zhuǎn)發(fā)到哪個 Handler,沒錯這里的 Handler 也是 http.Handler逗概,是同一個接口弟晚。用 http.Handler 來管理 http.Handler,我覺得這是一個非常優(yōu)雅的設(shè)計(jì)逾苫。
2.1 定義路由
路由可以使用兩種方式來定義卿城,一種是實(shí)現(xiàn) Handler,還有一種是使用 HandleFunc隶垮,這兩個概念在上一篇文章中我們已經(jīng)詳細(xì)討論過了藻雪,這里就不多說。
示例代碼中的路由使用 HandleFunc 來定義狸吞,這里我們來看一下路由具體是如何被定義的勉耀。我們進(jìn)入 HandleFunc 的源碼:
// server.go
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
發(fā)現(xiàn)實(shí)際上是調(diào)用了 DefaultServerMux
的 HandleFunc 方法,DefaultServerMux
實(shí)際上就是 ServeMux蹋偏,是 Go 的 HTTP 服務(wù)的默認(rèn)實(shí)現(xiàn)便斥。然后 DefaultServerMux
調(diào)用 Handle 方法來處理路由:
// server.go
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
}
}
路由由兩部分組成,一個是匹配 HTTP url 的 pattern威始,每個pattern 都代表著一類 HTTP 請求枢纠,都需要一個對應(yīng)的 handler 來處理。
DefaultServerMux
的 Handle 方法其實(shí)就做了一件事黎棠,在判斷路由和對應(yīng)的 handler 實(shí)現(xiàn)都沒問題晋渺,并且該路由沒有重復(fù)定義之后,就把這些路由都存到 map 中脓斩。所以 HTTP 服務(wù)的路由表其實(shí)就是一個 map木西。
完整流程如下,DefaultServerMux
簡稱為 DSM :
2.2 啟動 HTTP 服務(wù)
路由定義完成之后随静,就需要啟動服務(wù)了八千,就是下面這行代碼:
http.ListenAndServe(":8080", nil)
通常吗讶,第二個參數(shù)都會設(shè)置為 nil,設(shè)置為 nil 的時候恋捆,就會使用 Go 語言默認(rèn)的 HTTP 服務(wù)實(shí)現(xiàn)照皆。我們跟進(jìn)代碼的實(shí)現(xiàn):
// server.go
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
默認(rèn)情況下,會創(chuàng)建一個 Server沸停,Server 就是表示一個服務(wù)端膜毁, 其中定義了運(yùn)行一個 HTTP 服務(wù)所需要的全部參數(shù)以及必要的方法,如果不給 Server 傳入?yún)?shù) 愤钾,那么 Server 就使用默認(rèn)的參數(shù)運(yùn)行爽茴。
type Server struct {
// 服務(wù)端的 host 和 端口號
Addr string
Handler Handler // 默認(rèn)為 ServeMux
// 省略其他參數(shù)
}
然后 server 會調(diào)用 ListenAndServe 來啟動端口監(jiān)聽和請求接收,使用 net.Listen 來啟動監(jiān)聽端口:
// server.go
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)
}
然后調(diào)用 Server 的 Serve 方法來接收請求和處理請求绰垂。在 Serve 方法中,最關(guān)鍵的是下面這段代碼火焰,這里是一個 for 循環(huán)劲装,沒有結(jié)束條件,除非發(fā)生錯誤或者主動結(jié)束服務(wù)昌简,否則會一直處在接收請求的狀態(tài)占业。
// server.go#Serve
for {
rw, err := l.Accept()
if err != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
//......
return err
}
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)
}
接收到請之后,會為每一個請求創(chuàng)建一個 conn 實(shí)例纯赎,conn 表示服務(wù)端的一個 HTTP 連接谦疾,并啟動一個新的 goroutine 來處理這個請求。
然后就進(jìn)入到 conn 的 serve 方法犬金。
因?yàn)樵谝粋€ HTTP 請求中念恍,有可能會出現(xiàn)多次請求的收發(fā),所以這里依然啟動了 for 的循環(huán)來接收請求數(shù)據(jù)晚顷。
for {
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)
}
// .. 去掉無關(guān)代碼
serverHandler{c.server}.ServeHTTP(w, w.req)
// .. 去掉無關(guān)代碼
}
在 conn 的 serve 方法中峰伙,其實(shí)就只做了兩件事,一件事讀取請求中的數(shù)據(jù)该默。然后是調(diào)用 ServeHTTP 方法瞳氓,進(jìn)入到 ServeHTTP 方法中:
// server.go
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)
}
判斷 handler 是否為 nil,如果為 nil栓袖,然后就直接使用 DefaultServeMux 的 ServeHTTP 方法來處理 HTTP 請求匣摘。
// server.go
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)
}
我們發(fā)現(xiàn)其實(shí)這個方法也只做了一件事,就是去上面路由表 muxEntry 中匹配路由裹刮,然后使用路由 的 Handler 調(diào)用 ServerHTTP 來真正的處理請求音榜。
完整的流程如下:
3. 小結(jié)
Go 服務(wù)默認(rèn)的 HTTP 處理流程其實(shí)不難理解,最難的地方在于滿屏都是 Handler 接口和 ServeHTTP 方法必指,理解 Handler 接口是理解整個流程的關(guān)鍵囊咏。
所有的 HTTP 請求都需要經(jīng)過 ServeHTTP 方法處理。而 Go 語言中的 ServeMux 實(shí)現(xiàn)了 Handler 接口,通過 url 找到對應(yīng)的路由梅割,然后在 ServeHTTP 中調(diào)用路由實(shí)現(xiàn)的 ServeHTTP 方法去真正處理對應(yīng)的請求霜第。
Go 語言對 HTTP 服務(wù)的抽象非常好,通過一個接口就把整個流程串起來了户辞。
文 / Rayjun