Go 語(yǔ)言net/http 包使用模式

譯注: 這篇文章的內(nèi)容非常基礎(chǔ),也非常容易理解。原文地址,感覺是最能清晰的講述了net/http包的用法的一篇,故翻譯一下共享之。

一切的基礎(chǔ):ServeMux 和 Handler

Go 語(yǔ)言中處理 HTTP 請(qǐng)求主要跟兩個(gè)東西相關(guān):ServeMuxHandler。

ServrMux 本質(zhì)上是一個(gè) HTTP 請(qǐng)求路由器(或者叫多路復(fù)用器,Multiplexor)。它把收到的請(qǐng)求與一組預(yù)先定義的 URL 路徑列表做對(duì)比,然后在匹配到路徑的時(shí)候調(diào)用關(guān)聯(lián)的處理器(Handler)。

處理器(Handler)負(fù)責(zé)輸出HTTP響應(yīng)的頭和正文。任何滿足了http.Handler接口的對(duì)象都可作為一個(gè)處理器。通俗的說,對(duì)象只要有個(gè)如下簽名的ServeHTTP方法即可:

ServeHTTP(http.ResponseWriter, *http.Request)

Go 語(yǔ)言的 HTTP 包自帶了幾個(gè)函數(shù)用作常用處理器,比如FileServer,NotFoundHandlerRedirectHandler。我們從一個(gè)簡(jiǎn)單具體的例子開始:

$ 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 函數(shù)中我們只用了 http.NewServeMux 函數(shù)來創(chuàng)建一個(gè)空的 ServeMux。
  • 然后我們使用 http.RedirectHandler 函數(shù)創(chuàng)建了一個(gè)新的處理器,這個(gè)處理器會(huì)對(duì)收到的所有請(qǐng)求,都執(zhí)行307重定向操作到 http://example.org
  • 接下來我們使用 ServeMux.Handle 函數(shù)將處理器注冊(cè)到新創(chuàng)建的 ServeMux,所以它在 URL 路徑/foo 上收到所有的請(qǐng)求都交給這個(gè)處理器。
  • 最后我們創(chuàng)建了一個(gè)新的服務(wù)器,并通過 http.ListenAndServe 函數(shù)監(jiān)聽所有進(jìn)入的請(qǐng)求,通過傳遞剛才創(chuàng)建的 ServeMux來為請(qǐng)求去匹配對(duì)應(yīng)處理器。

繼續(xù),運(yùn)行一下這個(gè)程序:

$ go run main.go
Listening...

然后在瀏覽器中訪問 http://localhost:3000/foo,你應(yīng)該能發(fā)現(xiàn)請(qǐng)求已經(jīng)成功的重定向了。

明察秋毫的你應(yīng)該能注意到一些有意思的事情:ListenAndServer 的函數(shù)簽名是 ListenAndServe(addr string, handler Handler) ,但是第二個(gè)參數(shù)我們傳遞的是個(gè) ServeMux。

我們之所以能這么做,是因?yàn)?ServeMux 也有 ServeHTTP 方法,因此它也是個(gè)合法的 Handler。

對(duì)我來說,將 ServerMux 用作一個(gè)特殊的Handler是一種簡(jiǎn)化。它不是自己輸出響應(yīng)而是將請(qǐng)求傳遞給注冊(cè)到它的其他 Handler。這乍一聽起來不是什么明顯的飛躍 - 但在 Go 中將 Handler 鏈在一起是非常普遍的用法。

自定義處理器(Custom Handlers)

讓我們創(chuàng)建一個(gè)自定義的處理器,功能是將以特定格式輸出當(dāng)前的本地時(shí)間:

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

這個(gè)例子里代碼本身并不是重點(diǎn)。

真正的重點(diǎn)是我們有一個(gè)對(duì)象(本例中就是個(gè)timerHandler結(jié)構(gòu)體,但是也可以是一個(gè)字符串、一個(gè)函數(shù)或者任意的東西),我們?cè)谶@個(gè)對(duì)象上實(shí)現(xiàn)了一個(gè) ServeHTTP(http.ResponseWriter, *http.Request) 簽名的方法,這就是我們創(chuàng)建一個(gè)處理器所需的全部東西。

我們把這個(gè)集成到具體的示例里:

//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函數(shù)中,我們像初始化一個(gè)常規(guī)的結(jié)構(gòu)體一樣,初始化了timeHandler,用 & 符號(hào)獲得了其地址。隨后,像之前的例子一樣,我們使用 mux.Handle 函數(shù)來將其注冊(cè)到 ServerMux。

現(xiàn)在當(dāng)我們運(yùn)行這個(gè)應(yīng)用,ServerMux 將會(huì)將任何對(duì) /time的請(qǐng)求直接交給 timeHandler.ServeHTTP 方法處理。

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

注意我們可以在多個(gè)路由中輕松的復(fù)用 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)
}

將函數(shù)作為處理器

對(duì)于簡(jiǎn)單的情況(比如上面的例子),定義個(gè)新的有 ServerHTTP 方法的自定義類型有些累贅。我們看一下另外一種方式,我們借助 http.HandlerFunc 類型來讓一個(gè)常規(guī)函數(shù)滿足作為一個(gè) Handler 接口的條件。

任何有 func(http.ResponseWriter, *http.Request) 簽名的函數(shù)都能轉(zhuǎn)化為一個(gè) HandlerFunc 類型。這很有用,因?yàn)?HandlerFunc 對(duì)象內(nèi)置了 ServeHTTP 方法,后者可以聰明又方便的調(diào)用我們最初提供的函數(shù)內(nèi)容。

如果你聽起來還有些困惑,可以嘗試看一下[相關(guān)的源代碼]http://golang.org/src/pkg/net/http/server.go?s=35455:35502#L1221()。你將會(huì)看到讓一個(gè)函數(shù)對(duì)象滿足 Handler 接口是非常簡(jiǎn)潔優(yōu)雅的。

讓我們使用這個(gè)技術(shù)重新實(shí)現(xiàn)一遍timeHandler應(yīng)用:

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

實(shí)際上,將一個(gè)函數(shù)轉(zhuǎn)換成 HandlerFunc 后注冊(cè)到 ServeMux 是很普遍的用法,所以 Go 語(yǔ)言為此提供了個(gè)便捷方式:ServerMux.HandlerFunc 方法。

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

func main() {
  mux := http.NewServeMux()

  mux.HandleFunc("/time", timeHandler)

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

絕大多數(shù)情況下這種用函數(shù)當(dāng)處理器的方式工作的很好。但是當(dāng)事情開始變得更復(fù)雜的時(shí)候,就會(huì)有些產(chǎn)生一些限制了。

你可能已經(jīng)注意到了,跟之前的方式不同,我們不得不將時(shí)間格式硬編碼到 timeHandler 的方法中。如果我們想從 main() 函數(shù)中傳遞一些信息或者變量給處理器該怎么辦?

一個(gè)優(yōu)雅的方式是將我們處理器放到一個(gè)閉包中,將我們要使用的變量帶進(jìn)去:

//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 函數(shù)現(xiàn)在有了個(gè)更巧妙的身份。除了把一個(gè)函數(shù)封裝成 Handler(像我們之前做到那樣),我們現(xiàn)在使用它來返回一個(gè)處理器。這種機(jī)制有兩個(gè)關(guān)鍵點(diǎn):

首先是創(chuàng)建了一個(gè)fn,這是個(gè)匿名函數(shù),將 format 變量封裝到一個(gè)閉包里。閉包的本質(zhì)讓處理器在任何情況下,都可以在本地范圍內(nèi)訪問到 format 變量。

其次我們的閉包函數(shù)滿足 func(http.ResponseWriter, *http.Request) 簽名。如果你記得之前我們說的,這意味我們可以將它轉(zhuǎn)換成一個(gè)HandlerFunc類型(滿足了http.Handler接口)。我們的timeHandler 函數(shù)隨后轉(zhuǎn)換后的 HandlerFunc 返回。

在上面的例子中我們已經(jīng)可以傳遞一個(gè)簡(jiǎn)單的字符串給處理器。但是在實(shí)際的應(yīng)用中可以使用這種方法傳遞數(shù)據(jù)庫(kù)連接、模板組,或者其他應(yīng)用級(jí)的上下文。使用全局變量也是個(gè)不錯(cuò)的選擇,還能得到額外的好處就是編寫更優(yōu)雅的自包含的處理器以便測(cè)試。

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

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

或者在返回時(shí),使用一個(gè)到 HandlerFunc 類型的隱式轉(zhuǎn)換:

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

你可能已經(jīng)在很多地方看到過 DefaultServeMux, 從最簡(jiǎn)單的 Hello World 例子,到 go 語(yǔ)言的源代碼中。

我花了很長(zhǎng)時(shí)間才意識(shí)到 DefaultServerMux 并沒有什么的特殊的地方。DefaultServerMux 就是我們之前用到的 ServerMux,只是它隨著 net/httpp 包初始化的時(shí)候被自動(dòng)初始化了而已。Go 源代碼中的相關(guān)行如下:

var DefaultServeMux = NewServeMux()

net/http 包提供了一組快捷方式來配合 DefaultServeMuxhttp.Handlehttp.HandleFunc。這些函數(shù)與我們之前看過的類似的名稱的函數(shù)功能一樣,唯一的不同是他們將處理器注冊(cè)到 DefaultServerMux ,而之前我們是注冊(cè)到自己創(chuàng)建的 ServeMux

此外,ListenAndServe在沒有提供其他的處理器的情況下(也就是第二個(gè)參數(shù)設(shè)成了 nil),內(nèi)部會(huì)使用 DefaultServeMux

因此,作為最后一個(gè)步驟,我們使用 DefaultServeMux 來改寫我們的 timeHandler應(yīng)用:

//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)
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容