Golang HTTP Server啟動流程簡析及常用Handler介紹
來自: http://blog.kifile.com/golang/2015/11/21/go_server_intro.html
服務器啟動流程簡析
不管是什么語言,對于一個HTTP SERVER,它所做的就是監聽指定端口的HTTP請求,然后根據HTTP請求中的報頭以及請求信息,進行處理,最后返回數據.
Golang自然也不例外,系統為我們提供了兩個方法 http.ListenAndServe(string, Handler) , http.ListenAndServeTLS(string, string, string, Handler) ,分別用于開啟HTTP服務器和HTTPS服務器.
啟動HTTP服務器
我們先看一下 http.ListenAndServe(string, Handler) 是如何實現服務器的開啟的.
Golang 首先根據addr和handler構造出一個Server類型的結構體,然后調用 net.Listen("tcp", string) 方法去對指定端口進行注冊tcp監聽,
真正處理HTTP請求的方法在
1 func (srv *Server) Serve(l net.Listener) error {
2 defer l.Close()
3 var tempDelay time.Duration // how long to sleep on accept failure
4 for {
5 rw, e := l.Accept()
6 if e != nil {
7 if ne, ok := e.(net.Error); ok && ne.Temporary() {
8 ... // Re-try if necessary.
9 continue
10 }
11 return e
12 }
13 tempDelay = 0
14 c, err := srv.newConn(rw)
15 if err != nil {
16 continue
17 }
18 c.setState(c.rwc, StateNew) // before Serve can return
19 go c.serve()
20 }
21 }
從上面的代碼我們可以看出,當系統針對指定的端口進行監聽后,就立刻進入一個無限循環的隊列中,不停的從端口中獲取消息,一旦獲取到消息之后,使用routine新開線程進行消息處理.
讓我們再看一下在異步線程中處理請求的流程,
1 func (c *conn) serve() {
2 origConn := c.rwc // copy it before it's set nil on Close or Hijack
3 defer func() {
4 ... // 處理錯誤信息
5 }()
6
7 if tlsConn, ok := c.rwc.(*tls.Conn); ok {
8 ... // 處理TLS類型的請求.
9 }
10
11 for {
12 ... // 讀取請求信息.
13
14 // 調用Handler處理消息
15 serverHandler{c.server}.ServeHTTP(w, w.req)
16
17 ... // 結束
18 }
19 }
可以看到,在serve方法中,首先定義了一個defer延遲回調,用于catch消息處理過程中可能產生的異常,避免服務器崩潰.然后系統之前構造的Server結構體中的Handler對象處理請求.
啟動HTTPS服務器
啟動HTTPS服務器的基本流程和啟動HTTP服務器的流程基本一致,不過由于HTTPS服務需要在握手時對請求簽名進行驗證,故在conn.serve方法中,會針對TLS協議進行驗證,之后再進入Handler處理流程.
常用Handler介紹
看完上面的代碼之后,我們會發現其實在啟動服務器之后,主線程不斷從TCP端口獲取新的請求,然后使用routine進行異步執行.服務器所起的作用其實就是一個不斷拉去消息的消息隊列.
而實際上真正處理業務邏輯的代碼是在注冊監聽時,隨端口參數一起傳入的handler對象(PS:當傳入的Handler為nil時,默認是用系統的 http.DefaultServeMux 作為處理對象.
下面我們將對一些常用的Handler對象進行介紹
Handler
Handler接口中有一個 ServeHTTP(ResponseWriter, *Request) 方法,當SERVER從TCP端口中獲取到新的請求時,會調用這個方法去執行具體,換而言之, ServeHTTP方法就是所有SERVER消息處理接口的入口函數.其他所有想要處理HTTP請求的方法都必須直接或間接通過這個接口實現.
ServeMux
ServeMux從名字上我們就可以看出它是一個路由器,對于一個SERVER,他的主入口Handler其實只有一個. 但是訪問不同的URL地址,我們又應該給予用于不同的回應.因此我們需要根據不同的URL地址,將請求分發給不同的Handler對象進行處理,ServeMux幫我們實現了這個功能.
為了讓ServeMux知道對于指定的URL地址應該使用哪個Handler對象處理,我們需要對Handler在ServeMux中進行提前注冊. ServeMux中有兩個方法 Handle 和 HandleFunc 分別用于注冊Handler對象和 func 對象.
系統默認的 http.DefaultServeMux 也是一個ServeMux類型的對象,通常我們也可以直接調用 http.Handle , 或者 http.HandleFunc 方法進行注冊.
通過查閱 ServeMux 的源碼,發現其進行URL路徑匹配的代碼如下:
1 func (mux *ServeMux) match(path string) (h Handler, pattern string) {
2 var n = 0
3 for k, v := range mux.m {
4 if !pathMatch(k, path) {
5 continue
6 }
7 if h == nil || len(k) > n {
8 n = len(k)
9 h = v.h
10 pattern = v.pattern
11 }
12 }
13 return
14 }
mux.m 對象中包含了通過 Handle 和 HandleFuc 注冊的Handler對象(PS:通過HandleFunc 注冊的func對象也會被包裝成一個Handler),因此這里其實就是對 已注冊的 Handler對象做一次遍歷, 尋找最優長度匹配的URL地址,然后返回正確的Handler對象,將請求信息分發給他進行處理.
HandlerFunc
Golang和Java有個很大的不同,在于Golang允許將方法作為變量使用,類似C中的方法指針,但是Java中是不能這么操作的.
有時候,某些HTTP請求的業務邏輯可以在單個方法內執行完成,此時專門為了一個業務邏輯去構建一個實現了Handler接口的結構體是一件很不值得的事情,
因此Golang中將定義了一個 HandlerFunc 的類型作為 func(http.ResponseWriter, *http.Request) 的別名,并為他實現了 Handler接口.方便我們直接使用方法處理HTTP請求.
StripPrefix
StripPrefix,顧名思義,他的作用就是去除URL地址前綴.
例如,對于一個原本請求的URL地址為’/api1/sample’ 的Handler對象,如果他被一個 prefix為 /api1 的 StripPrefix 包含,那么實際上分發給他的URL地址,就是 “/sample”.
在我看來StripPrefix其實應該和ServeMux共同使用.
可以試想一下,對于同一個功能模塊的Handler,他們處理的URL肯定都會擁有一個相同的前綴用于表明他們所屬的功能模塊,但是如果有一天功能模塊的名字變了,那么改動起來也是一件相當麻煩的事情.
但假如我們將這個功能模塊的所有Handler都注冊到同一個ServeMux對象中,然后通過StripPrefix統一去掉他們的前綴,這時候我們改動模塊名的時候,只需要更改ServeMux和StripPrefix的路徑就好了.
RedirectHandler
重定向接口,返回一個重定向之后的地址,供瀏覽器再次請求.
TimeoutHandler
超時訪問接口,
在執行ServeHTTP方法時,他并沒有直接在本線程內執行,而是新建了一個channel,然后在異步線程中執行請求,而后本線程等待channel消息用于判斷請求是否完成或超時.
FileServer
FileServer負責處理靜態資源,對于一個服務器而言,并不意味著一切資源都是以動態形式存在,例如圖片,視頻等還是以靜態文件的形式存在的.
通過http.FileServer方法,可以構建一個fileServe結構體,專門用于將請求地址分發到正確的靜態資源地址上.
總結
Golang 已經在官方代碼中為我們提供了一整套完備的服務器框架,我們可以根據自己的需求去實現自己的 HTTP 服務器.
</code></code></code></div>