Go 語言net/http 包使用模式

譯註: 這篇文章的內容很是基礎,也很是容易理解。原文地址,感受是最能清晰的講述了net/http包的用法的一篇,故翻譯一下共享之。golang

一切的基礎:ServeMux 和 Handler

Go 語言中處理 HTTP 請求主要跟兩個東西相關:ServeMuxHandler數據庫

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

處理器(Handler)負責輸出HTTP響應的頭和正文。任何知足了http.Handler接口的對象均可做爲一個處理器。通俗的說,對象只要有個以下簽名的ServeHTTP方法便可:bash

ServeHTTP(http.ResponseWriter, *http.Request)

Go 語言的 HTTP 包自帶了幾個函數用做經常使用處理器,好比FileServerNotFoundHandlerRedirectHandler。咱們從一個簡單具體的例子開始:服務器

$ mkdir handler-example
$ cd handler-example
$ touch main.go
//File: main.go
package main

import (
  "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來爲請求去匹配對應處理器。.net

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

$ go run main.go
Listening...

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

明察秋毫的你應該能注意到一些有意思的事情:ListenAndServer 的函數簽名是 ListenAndServe(addr string, handler Handler) ,可是第二個參數咱們傳遞的是個 ServeMux

咱們之因此能這麼作,是由於 ServeMux 也有 ServeHTTP 方法,所以它也是個合法的 Handler

對我來講,將 ServerMux 用做一個特殊的Handler是一種簡化。它不是本身輸出響應而是將請求傳遞給註冊到它的其餘 Handler。這乍一聽起來不是什麼明顯的飛躍 - 但在 Go 中將 Handler 鏈在一塊兒是很是廣泛的用法。

自定義處理器(Custom Handlers)

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

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))
}

這個例子裏代碼自己並非重點。

真正的重點是咱們有一個對象(本例中就是個timerHandler結構體,可是也能夠是一個字符串、一個函數或者任意的東西),咱們在這個對象上實現了一個 ServeHTTP(http.ResponseWriter, *http.Request) 簽名的方法,這就是咱們建立一個處理器所需的所有東西。

咱們把這個集成到具體的示例裏:

//File: main.go

package main

import (
  "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)
}

將函數做爲處理器

對於簡單的狀況(好比上面的例子),定義個新的有 ServerHTTP 方法的自定義類型有些累贅。咱們看一下另一種方式,咱們藉助 http.HandlerFunc 類型來讓一個常規函數知足做爲一個 Handler 接口的條件。

任何有 func(http.ResponseWriter, *http.Request) 簽名的函數都能轉化爲一個 HandlerFunc 類型。這頗有用,由於 HandlerFunc 對象內置了 ServeHTTP 方法,後者能夠聰明又方便的調用咱們最初提供的函數內容。

若是你聽起來還有些困惑,能夠嘗試看一下[相關的源代碼]http://golang.org/src/pkg/net...。你將會看到讓一個函數對象知足 Handler 接口是很是簡潔優雅的。

讓咱們使用這個技術從新實現一遍timeHandler應用:

//File: main.go
package main

import (
  "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() {
  mux := http.NewServeMux()

  // Convert the timeHandler function to a HandlerFunc type
  th := http.HandlerFunc(timeHandler)
  // And add it to the ServeMux
  mux.Handle("/time", th)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}

實際上,將一個函數轉換成 HandlerFunc 後註冊到 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.go
package main

import (
  "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(像咱們以前作到那樣),咱們如今使用它來返回一個處理器。這種機制有兩個關鍵點:

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

其次咱們的閉包函數知足 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))
  }
}

更便利的 DefaultServeMux

你可能已經在不少地方看到過 DefaultServeMux, 從最簡單的 Hello World 例子,到 go 語言的源代碼中。

我花了很長時間才意識到 DefaultServerMux 並無什麼的特殊的地方。DefaultServerMux 就是咱們以前用到的 ServerMux,只是它隨着 net/httpp 包初始化的時候被自動初始化了而已。Go 源代碼中的相關行以下:

var DefaultServeMux = NewServeMux()

net/http 包提供了一組快捷方式來配合 DefaultServeMuxhttp.Handlehttp.HandleFunc。這些函數與咱們以前看過的相似的名稱的函數功能同樣,惟一的不一樣是他們將處理器註冊到 DefaultServerMux ,而以前咱們是註冊到本身建立的 ServeMux

此外,ListenAndServe在沒有提供其餘的處理器的狀況下(也就是第二個參數設成了 nil),內部會使用 DefaultServeMux

所以,做爲最後一個步驟,咱們使用 DefaultServeMux 來改寫咱們的 timeHandler應用:

//File: main.go
package main

import (
  "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() {
  // Note that we skip creating the ServeMux...

  var format string = time.RFC1123
  th := timeHandler(format)

  // 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)
}
相關文章
相關標籤/搜索