negroni
是一個專一於 HTTP 中間件的庫。它小巧,無侵入,鼓勵使用標準庫net/http
的處理器(Handler
)。本文就來介紹一下這個庫。git
爲何要使用中間件?有一些邏輯代碼,如統計、日誌、調試等,每個處理器中都須要,若是一個個去添加太繁瑣了、容易出錯、容易遺漏。若是咱們要統計處理器耗時,能夠在每一個處理器中添加代碼統計耗時:github
package main import ( "fmt" "net/http" "time" ) func index(w http.ResponseWriter, r *http.Request) { start := time.Now() fmt.Fprintf(w, "home page") fmt.Printf("index elasped:%fs", time.Since(start).Seconds()) } func greeting(w http.ResponseWriter, r *http.Request) { start := time.Now() name := r.FormValue("name") if name == "" { name = "world" } fmt.Fprintf(w, "hello %s", name) fmt.Printf("greeting elasped:%fs\n", time.Since(start).Seconds()) } func main() { mux := http.NewServeMux() mux.HandleFunc("/", index) mux.HandleFunc("/greeting", greeting) http.ListenAndServe(":8000", mux) }
可是這個作法很是不靈活:golang
利用 Go 語言的閉包,咱們能夠將實際的處理器代碼封裝到一個函數中,在這個函數中執行額外的邏輯:瀏覽器
func elasped(h func(w http.ResponseWriter, r *http.Request)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path start := time.Now() h(w, r) fmt.Printf("path:%s elasped:%fs\n", path, time.Since(start).Seconds()) } } func index(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "home page") } func greeting(w http.ResponseWriter, r *http.Request) { name := r.FormValue("name") if name == "" { name = "world" } fmt.Fprintf(w, "hello %s", name) } func main() { mux := http.NewServeMux() mux.HandleFunc("/", elasped(index)) mux.HandleFunc("/greeting", elasped(greeting)) http.ListenAndServe(":8000", mux) }
咱們將額外的與處理器無關的代碼放在另外的函數中。註冊處理器函數時,咱們不直接使用原始的處理器函數,而是用elasped
函數封裝一層。實際上elasped
這樣的函數就是中間件。它封裝原始的處理器函數,返回一個新的處理器函數。從而能很方便在實際的處理邏輯先後插入代碼,便於添加、修改和維護。服務器
先安裝:微信
$ go get github.com/urfave/negroni
後使用:閉包
package main import ( "fmt" "net/http" "github.com/urfave/negroni" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }) n := negroni.Classic() n.UseHandler(mux) http.ListenAndServe(":3000", n) }
negroni
的使用很是簡單,它能夠很方便的與http.Handler
一塊兒使用。negroni.Classic()
提供了幾個經常使用的中間件:dom
negroni.Recovery
:恢復panic
,處理器代碼中有panic
會被這個中間件捕獲,程序不會退出;negroni.Logger
:日誌,記錄請求和響應的基本信息;negroni.Static
:在public
目錄提供靜態文件服務。調用n.UseHandler(mux)
,將這些中間件應用到多路複用器上。運行,在瀏覽器中輸入localhost:3000
,查看控制檯輸出:函數
$ go run main.go [negroni] 2020-06-22T06:48:53+08:00 | 200 | 20.9966ms | localhost:3000 | GET / [negroni] 2020-06-22T06:48:54+08:00 | 200 | 0s | localhost:3000 | GET /favicon.ico
negroni.Handler
接口negroni.Handler
讓咱們對中間件的執行流程有更靈活的控制:學習
type Handler interface { ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) }
咱們編寫的中間件簽名必須是func(http.ResponseWriter,*http.Request,http.HandlerFunc)
,或者實現negroni.Handler
接口:
func RandomMiddleware(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { if rand.Int31n(100) <= 50 { fmt.Fprintf(w, "hello from RandomMiddleware") } else { next(w, r) } } func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }) n := negroni.New() n.Use(negroni.HandlerFunc(RandomMiddleware)) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
上面代碼中實現了一個隨機的中間件,有一半的機率直接從RandomMiddleware
這個中間件返回,一半的機率執行實際的處理器函數。運行程序,在瀏覽器中不停地刷新頁面localhost:3000
看看效果。
注意,實際上func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)
只是一個方便的寫法。在調用n.Use
時使用了negroni.HandlerFunc
作了一層封裝,而negroni.HandlerFunc
實現了negroni.Handler
接口:
// src/github.com/urfave/negroni/negroni.go type HandlerFunc func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) func (h HandlerFunc) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { h(rw, r, next) }
net/http
中也有相似的代碼,經過http.HandlerFunc
封裝func(http.ResponseWriter,*http.Request)
從而實現接口http.Handler
。
negroni.With
若是有多箇中間件,每一個都須要n.Use()
有些繁瑣。negroni
提供了一個With()
方法,它接受一個或多個negroni.Handler
參數,返回一個新的對象:
func Middleware1(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { fmt.Println("Middleware1 begin") next(w, r) fmt.Println("Middleware1 end") } func Middleware2(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { fmt.Println("Middleware2 begin") next(w, r) fmt.Println("Middleware2 end") } func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }) n := negroni.New() n = n.With( negroni.HandlerFunc(Middleware1), negroni.HandlerFunc(Middleware2), ) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
Run
Negroni
對象提供了一個方便的Run()
方法來運行服務器程序。它接受與http.ListenAndServe()
同樣的地址(Addr
)參數:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }) n := negroni.New() n.UseHandler(mux) n.Run(":3000") }
若是未指定端口,那麼嘗試使用PORT
環境變量。若是PORT
環境變量也未設置,那麼使用默認的端口:8080
。
http.Handler
使用negroni
很容易在net/http
程序中使用,negroni.Negroni
對象可直接做爲http.Handler
傳給相應的方法:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }) n := negroni.Classic() n.UseHandler(mux) s := &http.Server{ Addr: ":8080", Handler: n, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } s.ListenAndServe() }
negroni
內置了一些經常使用的中間件,可直接使用。
Static
negroni.Static
可在指定目錄中提供文件服務:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "hello world") }) n := negroni.New() n.Use(negroni.NewStatic(http.Dir("./public"))) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
在程序運行目錄下建立public
目錄,而後放入一些文件1.txt
,2.jpg
。程序運行以後,就能經過瀏覽器localhost:3000/1.txt
和localhost:3000/2.jpg
請求這些文件了。
另外須要特別注意一點,若是找不到對應的文件,Static
會將請求傳給下一個中間件或處理器函數。在上面的例子中就是hello world
。在瀏覽器中輸入localhost:3000/none-exist.txt
看看效果。
Logger
在快速開始中,咱們經過negroni.Classic()
已經使用過這個中間件了。咱們也能夠單獨使用,它能夠記錄請求的信息。咱們還能夠調用SetFormat()
方法設置日誌的格式:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "hello world") }) n := negroni.New() logger := negroni.NewLogger() logger.SetFormat("[{{.Status}} {{.Duration}}] - {{.Request.UserAgent}}") n.Use(logger) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
上面代碼中將日誌格式設置爲[{{.Status}} {{.Duration}}] - {{.Request.UserAgent}}
,即響應狀態、耗時和UserAgent
。
使用 Chrome 瀏覽器請求:
[negroni] [200 26.0029ms] - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36
Recovery
negroni.Recovery
能夠捕獲後續的中間件或處理器函數中出現的panic
,返回一個500
的響應碼:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { panic("internal server error") }) n := negroni.New() n.Use(negroni.NewRecovery()) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
請求時panic
的堆棧會顯示在瀏覽器中:
這在開發環境比較有用,可是生成環境中不能泄露這個信息。這時能夠設置PrintStack
字段爲false
:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { panic("internal server error") }) n := negroni.New() r := negroni.NewRecovery() r.PrintStack = false n.Use(r) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
除了在控制檯和瀏覽器中輸出panic
信息,Recovery
還提供了鉤子函數,能夠向其餘服務上報panic
,如Sentry/Airbrake
。固然上報的代碼要本身寫😄。
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { panic("internal server error") }) n := negroni.New() r := negroni.NewRecovery() r.PanicHandlerFunc = reportToSentry n.Use(r) n.UseHandler(mux) http.ListenAndServe(":3000", n) } func reportToSentry(info *negroni.PanicInformation) { fmt.Println("sent to sentry") }
設置PanicHandlerFunc
以後,發生panic
就會調用此函數。
咱們還能夠對輸出的格式進行設置,設置Formatter
字段爲negroni.HTMLPanicFormatter
能讓輸出更好地在瀏覽器中呈現:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { panic("internal server error") }) n := negroni.New() r := negroni.NewRecovery() r.Formatter = &negroni.HTMLPanicFormatter{} n.Use(r) n.UseHandler(mux) http.ListenAndServe(":3000", n) }
效果:
除了內置中間件外,negroni
還有不少第三方的中間件。完整列表看這裏:https://github.com/urfave/negroni#third-party-middleware。
咱們只介紹一個xrequestid
,它在每一個請求中增長一個隨機的Header
:X-Request-Id
。
安裝xrequestid
:
$ go get github.com/pilu/xrequestid
使用:
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "X-Request-Id is `%s`", r.Header.Get("X-Request-Id")) }) n := negroni.New() n.Use(xrequestid.New(16)) n.UseHandler(mux) n.Run(":3000") }
給每一個請求增長一個 16 字節的X-Request-Id
,處理器函數中將這個X-Request-Id
寫入響應中,最後呈如今瀏覽器中。運行程序,在瀏覽器中輸入localhost:3000
查看效果。
negroni
專一於中間件,沒有不少花哨的功能。無侵入性使得它很容易與標準庫net/http
和其餘的 Web 庫(如gorilla/mux
)一塊兒使用。
你們若是發現好玩、好用的 Go 語言庫,歡迎到 Go 每日一庫 GitHub 上提交 issue😄
歡迎關注個人微信公衆號【GoUpUp】,共同窗習,一塊兒進步~