中間件(通常)是一小段代碼延刘,它們接受一個請求魂那,對其進行處理,每個中間件只處理一件事情锌蓄,完成后將其傳遞給另一個中間件或最終處理程序升筏,這樣就做到了程序的解耦。如果沒有中間件那么我們必須在最終的處理程序中來完成這些處理操作瘸爽,這無疑會造成處理程序的臃腫和代碼復用率不高的問題您访。中間件的一些常見用例是請求日志記錄,Header操縱剪决、HTTP請求認證和ResponseWriter劫持等等灵汪。
畫外音:上面這段描述中間件的文字,跟我兩年前在Laravel源碼解析之中間件寫的幾乎一樣(其實這圖也是從那里拿過來的)柑潦。再次說明做開發(fā)時間長了以后掌握一些編程的思想有時候比掌握一門編程語言更重要享言,這不咱們就又用Go來寫中間件了。
創(chuàng)建中間件
接下來我們用Go創(chuàng)建中間件渗鬼,中間件只將http.HandlerFunc作為其參數(shù)览露,在中間件里將其包裝并返回新的http.HandlerFunc供服務器服務復用器調用。這里我們創(chuàng)建一個新的類型Middleware譬胎,這會讓最后一起鏈式調用多個中間件變的更簡單差牛。
type Middleware func(http.HandlerFunc) http.HandlerFunc
下面的中間件通用代碼模板讓我們平時編寫中間件變得更容易。
中間件代碼模板
中間件是使用裝飾器模式實現(xiàn)的银择,下面的中間件通用代碼模板讓我們平時編寫中間件變得更容易多糠,我們在自己寫中間件的時候只需要往樣板里填充需要的代碼邏輯即可。
func createNewMiddleware() Middleware {
? ? // 創(chuàng)建一個新的中間件
? ? middleware := func(next http.HandlerFunc) http.HandlerFunc {
? ? ? ? // 創(chuàng)建一個新的handler包裹next
? ? ? ? handler := func(w http.ResponseWriter, r *http.Request) {
? ? ? ? ? ? // 中間件的處理邏輯
......
? ? ? ? ? ? // 調用下一個中間件或者最終的handler處理程序
? ? ? ? ? ? next(w, r)
? ? ? ? }
? ? ? ? // 返回新建的包裝handler
? ? ? ? return handler
? ? }
? ? // 返回新建的中間件
? ? return middleware
}
使用中間件
我們創(chuàng)建兩個中間件浩考,一個用于記錄程序執(zhí)行的時長夹孔,另外一個用于驗證請求用的是否是指定的HTTP Method,創(chuàng)建完后再用定義的Chain函數(shù)把http.HandlerFunc和應用在其上的中間件鏈起來,中間件會按添加順序依次執(zhí)行搭伤,最后執(zhí)行到處理函數(shù)只怎。完整的代碼如下:
package main
import (
? ? "fmt"
? ? "log"
? ? "net/http"
? ? "time"
)
type Middleware func(http.HandlerFunc) http.HandlerFunc
// 記錄每個URL請求的執(zhí)行時長
func Logging() Middleware {
? ? // 創(chuàng)建中間件
? ? return func(f http.HandlerFunc) http.HandlerFunc {
? ? ? ? // 創(chuàng)建一個新的handler包裝http.HandlerFunc
? ? ? ? return func(w http.ResponseWriter, r *http.Request) {
? ? ? ? ? ? // 中間件的處理邏輯
? ? ? ? ? ? start := time.Now()
? ? ? ? ? ? defer func() { log.Println(r.URL.Path, time.Since(start)) }()
? ? ? ? ? ? // 調用下一個中間件或者最終的handler處理程序
? ? ? ? ? ? f(w, r)
? ? ? ? }
? ? }
}
// 驗證請求用的是否是指定的HTTP Method,不是則返回 400 Bad Request
func Method(m string) Middleware {
? ? return func(f http.HandlerFunc) http.HandlerFunc {
? ? ? ? return func(w http.ResponseWriter, r *http.Request) {
? ? ? ? ? ? if r.Method != m {
? ? ? ? ? ? ? ? http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
? ? ? ? ? ? ? ? return
? ? ? ? ? ? }
? ? ? ? ? ? f(w, r)
? ? ? ? }
? ? }
}
// 把應用到http.HandlerFunc處理器的中間件
// 按照先后順序和處理器本身鏈起來供http.HandleFunc調用
func Chain(f http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc {
? ? for _, m := range middlewares {
? ? ? ? f = m(f)
? ? }
? ? return f
}
// 最終的處理請求的http.HandlerFunc
func Hello(w http.ResponseWriter, r *http.Request) {
? ? fmt.Fprintln(w, "hello world")
}
func main() {
? ? http.HandleFunc("/", Chain(Hello, Method("GET"), Logging()))
? ? http.ListenAndServe(":8080", nil)
}
運行程序后會打開瀏覽器訪問http://localhost:8080會有如下輸出:
2020/02/07 21:07:52 / 359.503μs
2020/02/07 21:09:17 / 34.727μs
到這里怎么用Go編寫和使用中間件就講完怜俐,也就十分鐘吧身堡。不過這里更多的是探究實現(xiàn)原理,那么在生產(chǎn)環(huán)境怎么自己使用編寫的這些中間件呢拍鲤,我們接著往下看贴谎。
使用gorilla/mux應用中間件
上面我們探討了如何創(chuàng)建中間件,但是使用上每次用Chain函數(shù)鏈接多個中間件和處理程序還是有些不方便季稳,而且在上一篇文章中我們已經(jīng)開始使用gorilla/mux提供的Router作為路由器了擅这。好在gorrila.mux支持向路由器添加中間件,如果發(fā)現(xiàn)匹配項景鼠,則按照添加中間件的順序執(zhí)行中間件仲翎,包括其子路由器也支持添加中間件。
gorrila.mux路由器使用Use方法為路由器添加中間件铛漓,Use方法的定義如下:
func (r *Router) Use(mwf ...MiddlewareFunc) {
for _, fn := range mwf {
r.middlewares = append(r.middlewares, fn)
}
}
它可以接受多個mux.MiddlewareFunc類型的參數(shù)溯香,mux.MiddlewareFunc的類型聲明為:
type MiddlewareFunc func(http.Handler) http.Handler
跟我們上面定義的Middleware類型很像也是一個函數(shù)類型,不過函數(shù)的參數(shù)和返回值都是http.Handler接口浓恶,在《深入學習用 Go 編寫 HTTP 服務器》中我們詳細講過http.Handler它 是net/http中定義的接口用來表示處理 HTTP 請求的對象玫坛,其對象必須實現(xiàn)ServeHTTP方法。我們把上面說的中間件模板稍微更改下就能創(chuàng)建符合gorrila.mux要求的中間件:
func CreateMuxMiddleware() mux.MiddlewareFunc {
// 創(chuàng)建中間件
return func(f http.Handler) http.Handler {
// 創(chuàng)建一個新的handler包裝http.HandlerFunc
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 中間件的處理邏輯
......
// 調用下一個中間件或者最終的handler處理程序
f.ServeHTTP(w, r)
})
}
}
接下來问顷,我們把上面自定義的兩個中間件進行改造昂秃,然后應用到我們一直在使用的http_demo項目上禀梳,為了便于管理在項目中新建middleware目錄杜窄,兩個中間件分別放在log.go和http_method.go中
//middleware/log.go
func Logging() mux.MiddlewareFunc {
// 創(chuàng)建中間件
return func(f http.Handler) http.Handler {
// 創(chuàng)建一個新的handler包裝http.HandlerFunc
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 中間件的處理邏輯
start := time.Now()
defer func() { log.Println(r.URL.Path, time.Since(start)) }()
// 調用下一個中間件或者最終的handler處理程序
f.ServeHTTP(w, r)
})
}
}
// middleware/http_demo.go
func Method(m string) mux.MiddlewareFunc {
return func(f http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != m {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
f.ServeHTTP(w, r)
})
}
}
然后在我們的路由器中進行引用:
func RegisterRoutes(r *mux.Router) {
r.Use(middleware.Logging())// 全局應用
indexRouter := r.PathPrefix("/index").Subrouter()
indexRouter.Handle("/", &handler.HelloHandler{})
userRouter := r.PathPrefix("/user").Subrouter()
userRouter.HandleFunc("/names/{name}/countries/{country}", handler.ShowVisitorInfo)
userRouter.Use(middleware.Method("GET"))//給子路由器應用
}
再次編譯啟動運行程序后訪問
http://localhost:8080/user/names/James/countries/NewZealand
從控制臺里可以看到,記錄了這個請求的處理時長:
2020/02/08 09:29:50 Starting HTTP server...
2020/02/08 09:55:20 /user/names/James/countries/NewZealan 51.157μs
到這里我們探究完了編寫Web中間件的過程和原理算途,在實際開發(fā)中只需要根據(jù)自己的需求按照我們給的中間件代碼模板編寫中間件即可塞耕,在編寫中間件的時候也要注意他們的職責范圍,不要所有邏輯都往里放嘴瓤。
前期我也和很多小伙伴一樣扫外,到處收集了很多資料,后面發(fā)現(xiàn)很多重復的廓脆!上面面都是自己整理好的筛谚!現(xiàn)在BAT夢想成真,我就把資料貢獻出來給有需要的人停忿!
順便求一波關注驾讲,哈哈~各位小伙伴關注我后私信【Java】就可以免費領取噠!
作者:kevinyan
原文鏈接:https://juejin.im/post/5e3e9884f265da57375c3138