Go語言 Web開發(3)HTTP基礎流程分析(下)

來源:互聯網
上載者:User

在Go語言的http包中有兩個核心的功能:Conn,ServeMux。

Conn的goroutine
之前我們說過使用者的每一次請求Conn都是在一個新的goroutine去處理,相互不影響,不會被阻塞,這就是Go的高並發的體現。在 func (srv *Server) Serve(l net.Listener) error 函數中有這樣的描述

Serve accepts incoming connections on the Listener l, creating a new service goroutine for each.

具體代碼如下:

c := srv.newConn(rw)c.setState(c.rwc, StateNew) // before Serve can returngo c.serve(ctx) //傳遞到對應的handle

用戶端的每次請求都會建立一個Conn,這個Conn裡面儲存了該次請求的資訊,然後再傳遞到對應的handler,該handler中便可以讀取到相應的header資訊,這樣保證了每個請求的獨立性。

ServeMux的自訂
在 ServeHTTP 函數中若我們沒有指定handler(即http.ListenAndServe的第二個參數為nil),系統就會指定預設的handler:DefaultServeMux,通過路由器把本次請求的資訊傳遞到了後端的處理函數。那麼這個路由器是怎麼實現的呢?下面是我們沒有指定路由器時,系統自動設定預設的路由器代碼。

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {   handler := sh.srv.Handler   if handler == nil {      handler = DefaultServeMux //指定預設的 handler路由器   }   if req.RequestURI == "*" && req.Method == "OPTIONS" {      handler = globalOptionsHandler{}   }   handler.ServeHTTP(rw, req)}

關於 ServeMux 的解釋與結構如下所示

ServeMux是一個HTTP請求多工器。它將每個傳入請求的URL與登入模式的列表進行匹配,並調用與URL最匹配的模式的處理常式。模式名稱是固定的,有根的路徑,如“/favicon.ico”,或帶根的子樹,如“/ images /”(請注意尾部斜杠)。較長的模式優先於較短的模式,因此如果有“/ images /”和“/ images / thumbnails /”註冊的處理常式,則後面的處理常式將被調用以“/ images / thumbnails /”開頭的路徑和前者將收到“/ images /”子樹中任何其他路徑的請求。請注意,由於以斜杠結尾的模式命名為有根的子樹,因此模式“/”匹配所有未與其他登入模式比對的路徑,而不僅僅是具有Path ==“/”的URL。如果登入子樹並且收到命名子樹根但沒有其斜杠的請求,則ServeMux會將該請求重新導向到子樹根(添加尾部斜杠)。可以通過單獨註冊路徑而不使用尾部斜杠來覆蓋此行為。例如,註冊“/ images /”會導致ServeMux將“/ images”請求重新導向到“/ images /”,除非“/ images”已單獨註冊。模式可以選擇以主機名稱開頭,僅限制與該主機上的URL匹配。特定於主機的模式優先於一般模式,因此處理常式可以註冊兩個模式“/ codesearch”和“[codesearch.google.com/](http://codesearch.google.com/)”,而無需接管“[http://www.google.com/](http://www.google.com/)”的請求”。ServeMux還負責清理URL請求路徑,重新導向包含的任何請求。或..元素或重複斜杠到等效的,更清晰的URL。type ServeMux struct {   mu    sync.RWMutex //  鎖,由於請求涉及到並發處理,因此這裡需要一個鎖機制   m     map[string]muxEntry // 路由規則,一個 string 對應一個 mux 實體,這裡的 string 就是註冊的路由   hosts bool //  是否有任何模式包含主機名稱}

muxEntry結構如下所示

type muxEntry struct {   h       Handler // 這個路由運算式對應哪個 handler   pattern string // 模式}

接下來看看Handler的解釋和定義

處理常式響應HTTP請求。ServeHTTP應該將回複標題和資料寫入ResponseWriter,然後返回。返回請求完成的訊號;在完成ServeHTTP調用之後或同時使用ResponseWriter或從Request.Body讀取是無效的。根據HTTP用戶端軟體,HTTP協議版本以及用戶端和Go伺服器之間的任何中介,可能無法在寫入ResponseWriter後從Request.Body讀取。謹慎的處理常式應首先閱讀Request.Body,然後回複。除了閱讀本文外,處理常式不應修改提供的請求。如果ServeHTTP發生panic,伺服器(ServeHTTP的調用者)假定panic的影響與活動請求隔離。它恢複了panic,將堆疊追蹤記錄到伺服器錯誤日誌,並關閉網路連接或發送HTTP / 2 RST_STREAM,具體取決於HTTP協議。要中止處理常式以便用戶端看到響應中斷但伺服器不記錄錯誤,請使用值ErrAbortHandler進行panic。type Handler interface {   ServeHTTP(ResponseWriter, *Request) //路由實現器}

在我們之前寫的Web程式中,並沒有實現 Handler 介面的 ServeHTTP 方法,那我們為什麼還可以使用該方法來訪問路由呢?因為我們使用了 http.HandleFunc 函數。
我們來看看這個函數的具體實現

//HandleFunc在DefaultServeMux中註冊給定模式的處理函數。func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {   DefaultServeMux.HandleFunc(pattern, handler)}

這裡 DefaultServeMux.HandleFunc 調用的是下面這個函數

//  HandleFunc為給定模式註冊處理函數。func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {   mux.Handle(pattern, HandlerFunc(handler)) }

上面的HandlerFunc(handler) 調用的是以下的代碼,我們調用了HandlerFunc(f),強制類型轉換f成為HandlerFunc類型,這樣f就擁有了ServHTTP方法。

//HandlerFunc類型是一個允許將普通函數用作HTTP處理常式的適配器。 如果f是具有適當簽名的函數,則HandlerFunc(f)是一個調用f的Handler。type HandlerFunc func(ResponseWriter, *Request)// ServeHTTP calls f(w, r).func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {   f(w, r) //調用 HandlerFunc}

另外 mux.Handle調用如下函數,根據給定的模式註冊對應的handler和路由規則,若以存在此規則就會panic

func (mux *ServeMux) Handle(pattern string, handler Handler) {   mux.mu.Lock()   defer mux.mu.Unlock()   if pattern == "" {      panic("http: invalid pattern")   }   if handler == nil {      panic("http: nil handler")   }   if _, exist := mux.m[pattern]; exist {      panic("http: multiple registrations for " + pattern)   }   if mux.m == nil {      mux.m = make(map[string]muxEntry)   }   mux.m[pattern] = muxEntry{h: handler, pattern: pattern}   if pattern[0] != '/' {      mux.hosts = true   }}

經過上面的流程,我們就通過 http.HandleFunc("/",myGoWebService) 在設定了ServeHTTP中儲存好了相應的路由規則。

路由器裡面儲存好了相應的路由規則之後,那麼伺服器接收到具體的請求後又是怎麼根據url選擇對應的handler呢?
路由器接收到請求之後調用 mux.handler(r).ServeHTTP(w, r),根據規則分發的邏輯代碼就是在mux.handler(r)中實現,相關代碼如下:

//handler是Handler的主要實現。除了CONNECT方法之外,已知路徑是規範形式。func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {   mux.mu.RLock()   defer mux.mu.RUnlock()   // 特定於主機的模式優先於通用模式   if mux.hosts {      h, pattern = mux.match(host + path)   }   if h == nil {      h, pattern = mux.match(path)   }   if h == nil {      h, pattern = NotFoundHandler(), ""   }   return}

在上面代碼中使用match函數,根據使用者請求的URL和路由器裡面儲存的map去匹配的,當匹配到之後返回儲存的handler,調用這個handler的ServHTTP介面就可以執行到相應的函數了。

自訂路由功能
那麼如果我們要自訂路由功能,那要怎麼做呢?我們可以自己定義一個Handler,實現它的ServeHTTP 方法

import (   "net/http"   "fmt")func main() {   mux := &MyMux{}   http.ListenAndServe(":9090",mux)}type MyMux struct {}func (p *MyMux)ServeHTTP(rw http.ResponseWriter,r *http.Request){   if r.URL.Path == "/" {      myGoWebService(rw,r)      return   }   http.NotFound(rw,r)   return}func myGoWebService(rw http.ResponseWriter,request *http.Request)  {   fmt.Fprintf(rw,"Hello GoLang")}

若我們輸入http://localhost:9090/,那麼瀏覽器就會輸出下面結果 Hello GoLang,若我們輸入的地址後面不是“/”,那麼就會出現404 page not found

總結:

當我們調用 http.HandleFunc("/",myGoWebService)時,底層實際上按照順序做了以下幾件事情
①調用了DefaultServerMux的HandleFunc
②調用了DefaultServerMux的Handle
③往DefaultServeMux的map[string]muxEntry中增加對應的handler和路由規則

當我們調用 http.ListenAndServe(":9090",nil) 時,底層實際上按照順序做了以下幾件事情
①初始化一個server對象
②調用Server的ListenAndServe()
③調用net.Listen("tcp", addr)監聽連接埠
④啟動一個for{}迴圈,在這個迴圈體內通過Listener接收請求
⑤對每個請求執行個體化一個Conn,並且開啟一個goroutine為這個請求進行服務go c.serve()
⑥讀取每個請求的內容w, err := c.readRequest()
⑦判斷handler是否為空白,如果沒有設定handler(這個例子就沒有設定handler),handler就設定為DefaultServeMux
⑧調用handler的ServeHttp:serverHandler{c.server}.ServeHTTP(w, w.req)

最後當用戶端發送請求,根據url選擇對應的handler,並且進入到這個handler的ServeHTTP :mux.handler(r).ServeHTTP(w, r)

參考書籍:《Go Web編程》

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.