Golang HTTP Server啟動流程簡析及常用Handler介紹

rrpi8494 8年前發布 | 33K 次閱讀 HTTP Go語言 Web服務器

來自: 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>

 本文由用戶 rrpi8494 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!