Go HTTP請求處理——net/http包

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

Go 語言中處理 HTTP 要求主要跟兩個東西相關:ServeMux Handler

一、ServeMux Handler

        ServrMux 本質上是一個 HTTP 要求路由器(或者叫多工器,Multiplexor),它把收到的請求與一組預先定義的 URL 路徑列表做對比,然後在匹配到路徑的時候調用關聯的處理器(Handler)。

        Handler(處理器)負責輸出HTTP響應的頭和本文,任何滿足了http.Handler介面的對象都可作為一個處理器。通俗的說,對象只要有個如下籤名的ServeHTTP方法即可:

ServeHTTP(http.ResponseWriter, *http.Request)

     Go語言的HTTP 包內建了幾個函數用作常用處理器,比如FileServer,NotFoundHandler 和 RedirectHandler。我們從一個簡單具體的例子開始:

//File: main.gopackage mainimport (  "log"  "net/http")func main() {  mux := http.NewServeMux()  rh := http.RedirectHandler("http://example.org", 307)  mux.Handle("/foo", rh)  log.Println("Listening...")  http.ListenAndServe(":3000", mux)}

        在 main 函數中我們只用了 http.NewServeMux 函數來建立一個空的 ServeMux。
       然後我們使用 http.RedirectHandler 函數建立了一個新的處理器,這個處理器會對收到的所有請求,都執行307重新導向操作到 http://example.org。
       接下來我們使用 ServeMux.Handle 函數將處理器註冊到新建立的 ServeMux,所以它在 URL 路徑/foo 上收到所有的請求都交給這個處理器。
        最後我們建立了一個新的伺服器,並通過 http.ListenAndServe 函數監聽所有進入的請求,通過傳遞剛才建立的 ServeMux 來為請求去匹配對應處理器。

        繼續,運行一下這個程式:

$ go run main.goListening...

       然後在瀏覽器中訪問 http://localhost:3000/foo,你應該能發現請求已經成功的重新導向了。

       明察秋毫的你應該能注意到一些有意思的事:ListenAndServer 的函數簽名是 ListenAndServe(addr string, handler Handler) ,但是第二個參數我們傳遞的是個 ServeMux。我們之所以能這麼做,是因為 ServeMux 也有 ServeHTTP 方法,因此它也是個合法的 Handler。

       對我來說,將 ServerMux 用作一個特殊的Handler是一種簡化。它不是自己輸出響應而是將請求傳遞給註冊到它的其他 Handler。這乍一聽起來不是什麼明顯的飛躍,但在 Go 中將 Handler 鏈在一起是非常普遍的用法。

二、DefaultServeMux

        DefaultServeMux 是一種ServeMux,它會隨著 net/http 包初始化被自動初始化。net/http 包提供了一組快捷函數(http.Handlehttp.HandleFunc)來配合 DefaultServeMux,這些函數將處理器註冊到 DefaultServerMux。當ListenAndServe在沒有提供其他的處理器的情況下(也就是第二個參數設成了 nil),內部會使用 DefaultServeMux。

        下面是一個 DefaultServeMux 例子:

package mainimport (  "log"  "net/http"  "time")func timeHandler(w http.ResponseWriter, r *http.Request) {  tm := time.Now().Format(time.RFC1123)  w.Write([]byte("The time is: " + tm))}func main() {  // Note that we skip creating the ServeMux...  th := http.HandlerFunc(timeHandler)  // We use http.Handle instead of mux.Handle...  http.Handle("/time", th)  log.Println("Listening...")  // And pass nil as the handler to ListenAndServe.  http.ListenAndServe(":3000", nil)}

        這個例子裡使用了DefaultServeMux,而沒有顯示聲明ServeMux。在代碼中我們有一個對象(本例中就是個timerHandler函數,但是也可以是一個字串、一個結構體或者任意的東西),我們在這個函數內實現了一個 ServeHTTP(http.ResponseWriter, *http.Request) 簽名的方法,這就是我們建立一個處理器所需的全部東西。

        任何有 func(http.ResponseWriter, *http.Request) 簽名的函數都能轉化為一個 HandlerFunc 類型。這很有用,因為 HandlerFunc 對象內建了 ServeHTTP 方法,後者可以聰明又方便的調用我們最初提供的函數內容。

        如果你聽起來還有些困惑,可以嘗試看一下[相關的原始碼],你將會看到讓一個函數對象滿足 Handler 介面是非常簡潔優雅的。

三、顯式使用ServeMux

      實際上,將一個函數轉換成 HandlerFunc 後註冊到 ServeMux 是很普遍的用法。當顯式的使用ServeMux Go 語言有個更加方便的ServerMux.HandlerFunc函數。

        我們使用便捷方式重寫 main() 函數看起來是這樣的:

func main() {  mux := http.NewServeMux()  mux.HandleFunc("/time", timeHandler)  log.Println("Listening...")  http.ListenAndServe(":3000", mux)}

        絕大多數情況下這種用函數當處理器的方式工作的很好。但是當事情開始變得更複雜的時候,就會有些產生一些限制了。

        你可能已經注意到了,跟之前的方式不同,我們不得不將時間格式寫入程式碼到 timeHandler 的方法中。如果我們想從 main() 函數中傳遞一些資訊或者變數給處理器該怎麼辦?

        一個優雅的方式是將我們處理器放到一個閉包中,將我們要使用的變數帶進去:

//File: main.gopackage mainimport (  "log"  "net/http"  "time")func timeHandler(format string) http.Handler {  fn := func(w http.ResponseWriter, r *http.Request) {    tm := time.Now().Format(format)    w.Write([]byte("The time is: " + tm))  }  return http.HandlerFunc(fn)}func main() {  mux := http.NewServeMux()  th := timeHandler(time.RFC1123)  mux.Handle("/time", th)  log.Println("Listening...")  http.ListenAndServe(":3000", mux)}

        timeHandler 函數現在有了個更巧妙的身份。除了把一個函數封裝成 Handler(像我們之前做到那樣),我們現在使用它來返回一個處理器。這種機制有兩個關鍵點:

        (1)首先是建立了一個fn,這是個匿名函數,將 format 變數封裝到一個閉包裡。閉包的本質讓處理器在任何情況下,都可以在本地範圍內訪問到 format 變數。

        (2)其次我們的閉包函數滿足 func(http.ResponseWriter, *http.Request) 簽名。如果你記得之前我們說的,這意味我們可以將它轉換成一個HandlerFunc類型(滿足了http.Handler介面)。我們的timeHandler 函數隨後轉換後的 HandlerFunc 返回。

        在上面的例子中我們已經可以傳遞一個簡單的字串給處理器。但是在實際的應用中可以使用這種方法傳遞資料庫連接、模板組,或者其他應用級的上下文。使用全域變數也是個不錯的選擇,還能得到額外的好處就是編寫更優雅的自包含的處理器以便測試。

        你也可能見過相同的寫法,像這樣:

func timeHandler(format string) http.Handler {  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {    tm := time.Now().Format(format)    w.Write([]byte("The time is: " + tm))  })}

        或者在返回時,使用一個到 HandlerFunc 類型的隱式轉換:

func timeHandler(format string) http.HandlerFunc {  return func(w http.ResponseWriter, r *http.Request) {    tm := time.Now().Format(format)    w.Write([]byte("The time is: " + tm))  }}

四、自訂Handler

        讓我們建立一個自訂的處理器,功能是將以特定格式輸出當前的本地時間:

//File: main.gopackage mainimport (  "log"  "net/http"  "time")type timeHandler struct {  format string}func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {  tm := time.Now().Format(th.format)  w.Write([]byte("The time is: " + tm))}func main() {  mux := http.NewServeMux()  th := &timeHandler{format: time.RFC1123}  mux.Handle("/time", th)  log.Println("Listening...")  http.ListenAndServe(":3000", mux)}

        main函數中,我們像初始化一個常規的結構體一樣,初始化了timeHandler,用 & 符號獲得了其地址。隨後,像之前的例子一樣,我們使用 mux.Handle 函數來將其註冊到 ServerMux。

        現在當我們運行這個應用,ServerMux 將會將任何對 /time的請求直接交給 timeHandler.ServeHTTP 方法處理。

        訪問一下這個地址看一下效果:http://localhost:3000/time

        注意我們可以在多個路由中輕鬆的複用 timeHandler:

func main() {  mux := http.NewServeMux()  th1123 := &timeHandler{format: time.RFC1123}  mux.Handle("/time/rfc1123", th1123)  th3339 := &timeHandler{format: time.RFC3339}  mux.Handle("/time/rfc3339", th3339)  log.Println("Listening...")  http.ListenAndServe(":3000", mux)}

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.