本文主要講解go語言web編程中的路由與http服務基本原理。
首先,使用go語言啟動一個最簡單的http服務:
package main
import (
"log"
"net/http"
)
func main() {
http.HandleFunc("/", sayHello)
log.Println("server running...")
log.Fatal(http.ListenAndServe("localhost:4000", nil))
}
func sayHello(writer http.ResponseWriter, req *http.Request) {
writer.Write([]byte("hello world!"))
}
編譯,運行,瀏覽器訪問 http://localhost:4000/ ,輸出 hello world! 。
總的來說,這段代碼只是做了兩件事情,第一,注冊路由,指定客戶端請求路徑對應的響應函數:
http.HandleFunc("/", sayHello)
第二,啟動http服務,監聽端口,接受並響應客戶端請求:
http.ListenAndServe("localhost:4000", nil)
先看第一件事情——注冊路由,指定請求路徑對應的響應函數。
首先看 http.HandleFunc() 函數源碼:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
其中 DefaultServeMux 是go的默認路由器,所以注冊路由實際上是由路由器進行的,http.HandleFunc() 函數只是對它進行封裝,那么路由器的結構是怎么樣的呢?
源碼可見:
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry
hosts bool
}
type muxEntry struct {
h Handler
pattern string
}
其中 ServeMux 結構中的 map[string]muxEntry 就是用來保存請求路徑與響應函數之間的映射。從 muxEntry 結構定義可知,響應函數的類型為 Handler,而Handler實際上是一個接口類型,源碼如下:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
所以,響應函數需要實現這個接口,才能進行路由注冊。
源碼中聲明了一個 HandlerFunc 類型,就實現了 Handler 接口:
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
所以,只要我們的響應函數滿足結構 func (http.ResponseWriter, *http.Request) ,即可進行路由注冊,注冊路由時,路由器會將其類型強制轉換為 HandlerFunc 。其中,http.ResponseWriter參數包含了響應頭、響應數據等響應相關信息,而http.Request參數則包含了請求頭、請求參數等請求相關信息。
再看第二件事情,啟動http服務,監聽端口,接受並響應客戶端請求。
首先看 http.ListenAndServe() 函數源碼:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
其中 Server 即為http服務器類型,其結構如下(省略了部分字段):
type Server struct {
Addr string
Handler Handler
......
}
其中 Addr 為服務器監聽的ip與端口字符串,Handler 為路由器,指定其為 nil 時,go會使用它的默認路由器 DefaultServeMux (調用 http.HandleFunc() 方法注冊路由時就是注冊到這個默認的路由器)。
服務器監聽端口,接受客戶端請求,並做出響應,這個過程可借助《go web編程》中的一張圖示來幫助理解:
圖中有兩個紅色矩形標記,第一個,說明針對客戶端的每一個請求,go都會使用一個Goroutine進行響應,保證每個請求都能獨立,相互不會阻塞,可以高效響應網絡事件;第二,最終調用默認路由器的 ServeHTTP(w ResponseWriter, r *Request) 方法進行路由,從請求路徑與響應函數的映射中找到對應的handler,最后調用handler的 ServeHTTP(w ResponseWriter, r *Request) 方法,從上面 HandlerFunc 類型的 ServeHTTP(w ResponseWriter, r *Request) 方法可知,其實最后調用的就是我們注冊路由時定義的響應函數本身。
使用go默認路由器的不足之處是,不滿足RESTful規則,而且對請求路徑的路由只支持絕對匹配,不支持正則匹配。如果想設計一些特殊、簡便的路由,需要設計一個自定義路由器,並讓go的http服務器使用這個自定義路由器。關於自定義路由器的設計,可以參考筆者另一篇博文:go web編程——自定義路由設計 。
借鑒:
《Go Web編程》