這篇文章出現的理由是業務上需要創建一個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的Body Write([]byte) (int, error) // 這個方法根據HTTP State Code來寫Response的Header WriteHeader(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 // 是否使用chunk wroteHeader bool // header是否已經執行過寫操作 wroteContinue bool // 100 Continue response was written header Header // 返回的http的Header written 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() Header func (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獲取Handler func (mux *ServeMux) handler(r *Request) Handler //根據Request獲取Handler,內部實現調用match func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) //!!這個說明,ServeHttp也實現了Handler接口,它實際上也是一個Handler!內部實現調用handler func (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更確切)如果為空則設置為DefaultServeMux ReadTimeout 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服務,內部調用Serve func (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的。請期待之。