Go 語言的 http 包工作模式

SunLazar 8年前發布 | 9K 次閱讀 HTTP Go語言 Google Go/Golang開發

一切的基礎:ServeMux 和 Handler

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

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

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

ServeHTTP(http.ResponseWriter, *http.Request)

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

$ 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("

log.Println("Listening...") http.ListenAndServe(":3000", mux) }</code></pre>

快速地過一下代碼:

  • 在 main 函數中我們只用了 http.NewServeMux 函數來創建一個空的 ServeMux 。

  • 然后我們使用 http.RedirectHandler 函數創建了一個新的處理器,這個處理器會對收到的所有請求,都執行307重定向操作到 http://example.org 。

  • 接下來我們使用 ServeMux.Handle 函數將處理器注冊到新創建的 ServeMux ,所以它在 URL 路徑 /foo 上收到所有的請求都交給這個處理器。

  • 最后我們創建了一個新的服務器,并通過 http.ListenAndServe 函數監聽所有進入的請求,通過傳遞剛才創建的 ServeMux 來為請求去匹配對應處理器。

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

$ go run main.go
Listening...

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

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

我們之所以能這么做,是因為 ServeMux 也有 ServeHTTP 方法,因此它也是個合法的處理器。

For me it simplifies things to think of a ServeMux as just being a special kind of handler, which instead of providing a response itself passes the request on to a second handler. This isn't as much of a leap as it first sounds – chaining handlers together is fairly commonplace in Go.

對我來說,將 ServerMux 作為一個特殊的處理器是一種簡化。我們不用在第二個 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)) }</code></pre>

這個例子里代碼本身并不是重點。

真正的重點是我們有一個對象(本例中就是個 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) }</code></pre>

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

將函數作為處理器

對于簡單的情況(比如上面的例子),定義個新的有 ServerHTTP 方法的自定義類型有些累贅。我們看一下另外一種方式,我們借助 http.HandleerFunc 類型來強制一個常規函數滿足成為 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) }</code></pre>

實際上,將一個函數轉換成 HandlerFunc 后注冊到 ServeMux 是很普遍的用法,所以 Go 語言為此提供了個便捷方式: ServerMux.HandlerFunc 方法。

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

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

mux.HandleFunc("/time", timeHandler)

log.Println("Listening...") http.ListenAndServe(":3000", mux) }</code></pre>

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

你可能已經注意到了,跟之前的方式不同,我們不得不將時間格式硬編碼到 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) }</code></pre>

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 語言的源代碼中。

It took me a long time to realise it isn't anything special. The DefaultServeMux is just a plain ol' ServeMux like we've already been using, which gets instantiated by default when the HTTP package is used. Here's the relevant line from the Go source:

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

var DefaultServeMux = NewServeMux()

net/http 包提供了一組快捷方式來配合 DefaultServeMux : http.Handle http.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) }</code></pre>

 

來自:https://segmentfault.com/a/1190000006812688

 

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