這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
這篇文章出現的理由是業務上需要建立一個Web Server。建立web是所有語言出現必須實現的功能之一了。在nginx+fastcgi+php廣為使用的今天,這裡我們不妨使用Go來進行web伺服器的搭建。
前言
使用Go搭建Web伺服器的包有很多,大致有下面幾種方法,直接使用net包,使用net.http包,使用第三方包(比如gorilla)。使用net包就需要從tcp層開始封裝,耗費人力物力極大,果斷捨棄。直接使用封裝好的net.http和第三方包才是上策。這裡我們就選擇了使用官方提供的net.http包來搭建web服務。另外附帶一句,gorilla的第三方包現在使用還是非常廣的,文檔也是比較全的,有興趣的同學可以考慮使用一下。
建議看這篇文章前先看一下net/http文檔 http://golang.org/pkg/net/http/
net.http包裡面有很多檔案,都是和http協議相關的,比如設定cookie,header等。其中最重要的一個檔案就是server.go了,這裡我們閱讀的就是這個檔案。
幾個重要概念
ResponseWriter: 產生Response的介面
Handler: 處理請求和產生返回的介面
ServeMux: 路由,後面會說到ServeMux也是一種Handler
Conn : 網路連接
具體分析
(具體的說明直接以注釋形式放在代碼中)
幾個介面:
Handler
type Handler interface {ServeHTTP(ResponseWriter, *Request) // 具體的邏輯函數}
實現了handler介面的對象就意味著往server端添加了處理請求的邏輯。
下面是三個介面(ResponseWriter, Flusher, Hijacker):
ResponseWriter, Flusher, Hijacker
// ResponseWriter的作用是被Handler調用來組裝返回的Response的type ResponseWriter interface {// 這個方法返回Response返回的Header供讀寫Header() Header// 這個方法寫Response的BodyWrite([]byte) (int, error)// 這個方法根據HTTP State Code來寫Response的HeaderWriteHeader(int)}// Flusher的作用是被Handler調用來將寫緩衝中的資料推給用戶端type Flusher interface {// 這個方法將寫緩衝中資料推送給用戶端Flush()}// Hijacker的作用是被Handler調用來關閉串連的type Hijacker interface {// 這個方法讓調用者主動管理串連Hijack() (net.Conn, *bufio.ReadWriter, error)}
response
實現這三個介面的結構是response(這個結構是http包私人的,在文檔中並沒有顯示,需要去看源碼)
// response包含了所有server端的http返回資訊type response struct {conn *conn // 儲存此次HTTP串連的資訊req *Request // 對應請求資訊chunking bool // 是否使用chunkwroteHeader bool // header是否已經執行過寫操作wroteContinue bool // 100 Continue response was writtenheader Header // 返回的http的Headerwritten int64 // Body的位元組數contentLength int64 // Content長度status int // HTTP狀態needSniff bool // 是否需要使用sniff。(當沒有設定Content-Type的時候,開啟sniff能根據HTTP body來確定Content-Type)closeAfterReply bool //是否保持長連結。如果用戶端發送的請求中connection有keep-alive,這個欄位就設定為false。requestBodyLimitHit bool //是否requestBody太大了(當requestBody太大的時候,response是會返回411狀態的,並把串連關閉) }
在response中是可以看到
func (w *response) Header() Headerfunc (w *response) WriteHeader(code int)func (w *response) Write(data []byte) (n int, err error)func (w *response) Flush()func (w *response) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error)
這麼幾個方法。所以說response實現了ResponseWriter,Flusher,Hijacker這三個介面
HandlerFunc
handlerFunc是經常使用到的一個type
// 這裡將HandlerFunc定義為一個函數類型,因此以後當調用a = HandlerFunc(f)之後, 調用a的ServeHttp實際上就是調用f的對應方法type HandlerFunc func(ResponseWriter, *Request)// ServeHTTP calls f(w, r).func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {f(w, r)}
這裡需要多回味一下了,這個HandlerFunc定義和ServeHTTP合起來是說明了什嗎?說明HandlerFunc的所有執行個體是實現了ServeHttp方法的。另,實現了ServeHttp方法就是什嗎?實現了介面Handler!
所以你以後會看到很多這樣的句子:
func AdminHandler(w ResponseWriter, r *Request) { ...}handler := HandlerFunc(AdminHandler)handler.ServeHttp(w,r)
請不要訝異,你明明沒有寫ServeHttp,怎麼能調用呢? 實際上調用ServeHttp就是調用AdminHandler。
好吧,理解這個也花了我較長時間,附帶上一個play.google寫的一個小例子
http://play.golang.org/p/nSt_wcjc2u
有興趣繼續研究的同學可以繼續實驗下去
如果你理解了HandlerFunc,你對下面兩個句子一定不會訝異了
func NotFound(w ResponseWriter, r *Request) { Error(w, "404 page not found", StatusNotFound) }func NotFoundHandler() Handler { return HandlerFunc(NotFound) }
下面接著看Server.go
ServerMux結構
它就是http包中的路由規則器。你可以在ServerMux中註冊你的路由規則,當有請求到來的時候,根據這些路由規則來判斷將請求分發到哪個處理器(Handler)。
它的結構如下:
type ServeMux struct {mu sync.RWMutex //鎖,由於請求設計到並發處理,因此這裡需要一個鎖機制m map[string]muxEntry // 路由規則,一個string對應一個mux實體,這裡的string就是我註冊的路由運算式}
下面看一下muxEntry
type muxEntry struct {explicit bool // 是否精確匹配h Handler // 這個路由運算式對應哪個handler}
看到這兩個結構就應該對請求是如何路由的有思路了:
當一個請求request進來的時候,server會依次根據ServeMux.m中的string(路由運算式)來一個一個匹配,如果找到了可以匹配的muxEntry,就取出muxEntry.h,這是個handler,調用handler中的ServeHTTP(ResponseWriter, *Request)來組裝Response,並返回。
ServeMux定義的方法有:
func (mux *ServeMux) match(path string) Handler //根據path擷取Handlerfunc (mux *ServeMux) handler(r *Request) Handler //根據Request擷取Handler,內部實現調用matchfunc (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) //!!這個說明,ServeHttp也實現了Handler介面,它實際上也是一個Handler!內部實現調用handlerfunc (mux *ServeMux) Handle(pattern string, handler Handler) //註冊handler方法func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) //註冊handler方法(直接使用func註冊)
在godoc文檔中經常見到的DefaultServeMux是http預設使用的ServeMux
var DefaultServeMux = NewServeMux()
如果我們沒有自訂ServeMux,系統預設使用這個ServeMux。
換句話說,http包外層(非ServeMux)中提供的幾個方法:
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {DefaultServeMux.HandleFunc(pattern, handler)}
實際上就是調用ServeMux結構內部對應的方法。
Server
下面還剩下一個Server結構
type Server struct {Addr string // 監聽的地址和連接埠Handler Handler // 所有請求需要調用的Handler(實際上這裡說是ServeMux更確切)如果為空白則設定為DefaultServeMuxReadTimeout time.Duration // 讀的最大Timeout時間WriteTimeout time.Duration // 寫的最大Timeout時間MaxHeaderBytes int // 要求標頭的最大長度TLSConfig *tls.Config // 配置TLS}
Server提供的方法有:
func (srv *Server) Serve(l net.Listener) error //對某個連接埠進行監聽,裡面就是調用for進行accept的處理了func (srv *Server) ListenAndServe() error //開啟http server服務,內部調用Servefunc (srv *Server) ListenAndServeTLS(certFile, keyFile string) error //開啟https server服務,內部調用Serve
當然Http包也直接提供了方法供外部使用,實際上內部就是執行個體化一個Server,然後調用ListenAndServe方法
func ListenAndServe(addr string, handler Handler) error //開啟Http服務func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error //開啟HTTPs服務
具體例子分析
下面根據上面的分析,我們對一個例子我們進行閱讀。這個例子搭建了一個最簡易的Server服務。當調用http://XXXX:12345/hello的時候頁面會返回“hello world”
func HelloServer(w http.ResponseWriter, req *http.Request) {io.WriteString(w, "hello, world!\n")}func main() {http.HandleFunc("/hello", HelloServer)err := http.ListenAndServe(":12345", nil)if err != nil {log.Fatal("ListenAndServe: ", err)}}
首先調用Http.HandleFunc
按順序做了幾件事:
1 調用了DefaultServerMux的HandleFunc
2 調用了DefaultServerMux的Handle
3 往DefaultServeMux的map[string]muxEntry中增加對應的handler和路由規則
其次調用http.ListenAndServe(":12345", 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 判斷header是否為空白,如果沒有設定handler(這個例子就沒有設定handler),handler就設定為DefaultServeMux
8 調用handler的ServeHttp
9 在這個例子中,下面就進入到DefaultServerMux.ServeHttp
10 根據request選擇handler,並且進入到這個handler的ServeHTTP
mux.handler(r).ServeHTTP(w, r)
11 選擇handler:
A 判斷是否有路由能滿足這個request(迴圈遍曆ServerMux的muxEntry)
B 如果有路由滿足,調用這個路由handler的ServeHttp
C 如果沒有路由滿足,調用NotFoundHandler的ServeHttp
後記
對於net.http包中server的理解是非常重要的。理清serverMux, responseWriter, Handler, HandlerFunc等常用結構和函數是使用go web的重要一步。個人感覺由於go中文檔較少,像這樣有點複雜的包,看godoc的效果就遠不如直接看代碼來的快和清晰了。實際上在理解了http包後,才會對godoc中出現的句子有所理解。後續還會寫一些文章關於使用net.http構建web server的。請期待之。