Go語言經典庫使用分析,未完待續,歡迎掃碼關注公衆号 flysnow_org
或者網站http://www.flysnow.org/,第一時間看後續系列。覺得有幫助的話,順手分享到朋友圈吧,感謝支援。
上一篇 Go語言經典庫使用分析(五)| Negroni 中間件(一) 中介紹了Negroni中間的入門使用和一些介紹,比如如何添加中間等,中間件的路由等。這一篇主要講原理,比如如何建構的中間處理鍊,如何編寫自己的中間件等。
Negroni Handler處理器
本質上來說
Negroni
是一個HTTP Handler,因為他實作了HTTP Handler接口,是以他可以被
http.ListenAndServe
使用,其次
Negroni
本身内部又有一套自己的Handler處理鍊,通過他們可以達到處理http請求的目的,這些Handler處理鍊中的處理器,就是一個個中間件。
func (n *Negroni) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
n.middleware.ServeHTTP(NewResponseWriter(rw), r)
}
複制
以上代碼,是
Negroni
實作了HTTP的Handler,這樣就和标注庫裡的http無縫整合了。實作了HTTP Handler,對于HTTP Request來說,就有一個統一的入口,所有的對HTTP Requet的處理,都會被
Negroni
通過
ServeHTTP
方法轉交給
Negroni
内部注冊的中間件。
type Handler interface {
ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)
}
type HandlerFunc func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)
複制
這是
Negroni
自己定義的Handler處理器,它和HTTP Handler非常相似,唯一不同的是多了一個
next
參數,這個
next
參數是組成中間件處理鍊的核心。
如何建構中間件處理鍊
我們已經知道了
Negroni
有自己的一套Handler中間件處理鍊,那麼這個處理鍊和如何建構的呢?要想解開這個謎底,我們先看下
Negroni
如何注冊一個中間件的。
type Negroni struct {
middleware middleware
handlers []Handler
}
func (n *Negroni) Use(handler Handler) {
if handler == nil {
panic("handler cannot be nil")
}
n.handlers = append(n.handlers, handler)
n.middleware = build(n.handlers)
}
複制
在我們調用
Use
方法的時候,會把
Negroni
的Handler存在自己的
handlers
字段中,這是一個Slice類型字段,可以儲存我們存放的Negroni Handler。同時會基于這個存放Negroni Handler的Slice建構中間件處理鍊
middleware
。
type middleware struct {
handler Handler
next *middleware
}
複制
middleware
struct有很簡單,有兩個字段,一個是目前的Negroni Handler,一個是指向下一個
middleware
的指針
next
。有了這樣一個
middleware
struct,就可以建構一個完美的中間件處理鍊了。
func build(handlers []Handler) middleware {
var next middleware
if len(handlers) == 0 {
return voidMiddleware()
} else if len(handlers) > 1 {
next = build(handlers[1:])
} else {
next = voidMiddleware()
}
return middleware{handlers[0], &next}
}
func voidMiddleware() middleware {
return middleware{
HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {}),
&middleware{},
}
}
複制
以上代碼就是建構一個中間件處理鍊的邏輯,這是一個遞歸的函數,邏輯比較簡單。
當
handlers
參數為空的時候,直接通過
voidMiddleware
函數傳回一個空的
middleware
。 當
handlers
參數隻有1個處理器的時候,建構的
milldeware
就沒有
next
了,是以
next
是通過
voidMiddleware
函數獲得的,然後再通過
return middleware{handlers[0], &next}
組成生成的
middleware
并傳回。
最後一種情況,就是有1個以上的Negroni Handler,那就通過
build
函數循環遞歸了,從第2個Handler開始,不停的往後遞歸處理,是以最先被添加的Negroni Handler會被放在中間件處理鍊的前面,也就意味着會被優先執行。
中間件如何被調用
建構好了處理器鍊,那麼這些中間件如何被調用的呢?前面的章節,我們講了Negroni是一個HTTP Handler,是以總的入口在
ServeHTTP
方法裡。
func (n *Negroni) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
n.middleware.ServeHTTP(NewResponseWriter(rw), r)
}
複制
從以上代碼可以看出,調用了
middleware.ServeHTTP
方法,這就是中間件被執行的開始。
func (m middleware) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
m.handler.ServeHTTP(rw, r, m.next.ServeHTTP)
}
複制
middleware.ServeHTTP
方法調用
middleware
中目前handler的
ServeHTTP
方法執行我們自己寫的中間件處理邏輯。
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)
}
複制
又回到這裡了吧,handler的
ServeHTTP
方法執行,本質上就是執行的我們自己定義的negroni.HandlerFunc。
目前的中間件被執行了,那麼下一個如何被觸發的?這就是我們自己定義的中間件函數中的next參數了。我們在自己的中間件處理結束後,如果覺得有必要,就需要調用next函數,繼續執行下一個中間件,如果我們不調用next函數,那麼中間件鍊的處理,到這裡就斷了。看一個自定義中間件的例子。
func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
start := time.Now()
next(rw, r)
//省略無關代碼
}
複制
這是Negroni内置的log中間件的實作,可以看到,它調用了
next(rw, r)
函數,讓中間件處理鍊繼續執行。這個
next(rw, r)
是什麼呢?其實前面我們講過。
func (m middleware) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
m.handler.ServeHTTP(rw, r, m.next.ServeHTTP)
}
複制
next
函數的調用,就是對下一個
middleware
的
ServeHTTP
的再次調用,這是一個遞歸,也是中間件處理鍊設計的巧妙之處,靈活控制,自由的選擇是否調用下個中間件。
Go語言經典庫使用分析,未完待續,歡迎掃碼關注公衆号 flysnow_org
或者網站http://www.flysnow.org/,第一時間看後續系列。覺得有幫助的話,順手分享到朋友圈吧,感謝支援。
http Handler和negroni Handler之間的轉換
在剛介紹Negroni的時候,我們知道,它是相容HTTP Handler的,Negroni可以直接把HTTP Handler轉換為Negroni HTTP,讓我們可以直接使用HTTP Handler作為Negroni的中間件,下面我們看下是如何轉換的。
func (n *Negroni) UseHandler(handler http.Handler) {
n.Use(Wrap(handler))
}
func (n *Negroni) UseHandlerFunc(handlerFunc func(rw http.ResponseWriter, r *http.Request)) {
n.UseHandler(http.HandlerFunc(handlerFunc))
}
複制
Negroni提供兩個方法,分别相容
http.Handler
和
http.HandlerFunc
,
UseHandlerFunc
方法本質上還是調用的
UseHandler
方法,
UseHandler
方法實作的重點就是
Wrap(handler)
,它完成了http.Handler到negroni.Handler的轉換。
func Wrap(handler http.Handler) Handler {
return HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
handler.ServeHTTP(rw, r)
next(rw, r)
})
}
複制
這個函數本質上是Negroni中間件的實作,它的代碼邏輯就是先執行
http.Handler
中間件的實作,然後調用
next(rw, r)
執行下一個中間件。
内置中間件介紹
Negroni内置了幾個中間件,當我們通過
Classic
函數建立一個
*Negroni
的時候,就會有
func Classic() *Negroni {
return New(NewRecovery(), NewLogger(), NewStatic(http.Dir("public")))
}
複制
NewRecovery
,
NewLogger
和
NewStatic
就是這幾個内置的中間件,都是實作了negroni handler而成的中間件,具體源代碼大家可以看下,這裡就不具體介紹了。
編寫自己的中間件
編寫自己的中間件,在Negroni可以采用兩種方式,一種是
http.Handler
的方式,這種方式優點是大家都熟悉,并且已經會了,缺點也有,就是不能控制中間件的處理鍊,預設是調用下一個中間件的,我們不能中斷。
另外一個就是實作
negroni.Handler
的方式,Negroni推薦的也是這種方式。
//Blog:www.flysnow.org
//Wechat:flysnow_org
func main() {
n := negroni.New()
n.UseFunc(printAuthorInfo)
router:=http.NewServeMux()
router.Handle("/",handler())
n.UseHandler(router)
n.Run(":1234")
}
func printAuthorInfo(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc){
fmt.Println("Blog:www.flysnow.org")
fmt.Println("Wechat:flysnow_org")
next(rw,r)
}
func handler() http.Handler{
return http.HandlerFunc(myHandler)
}
func myHandler(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "text/plain")
io.WriteString(rw,"Hello World")
}
複制
我們通過
printAuthorInfo
函數實作了一個Negroni Handler中間件,這個中間件很簡單,隻是列印一些作者資訊,然後就執行下一個中間件。
使用的時候,我們通過
n.UseFunc(printAuthorInfo)
注冊這個中間件,我們啟動服務,通路
http://localhost:1234
的時候,就可以在控制台看到如下資訊:
Blog:www.flysnow.org
Wechat:flysnow_org
複制
為了舉例,我這個中間件比較簡單,大家可以根據自己的需求實作滿足自己業務的中間件。
一些中間件介紹
Github上有很多專門為Negroni開發的第三方中間件,以配合Negroni的使用,比如gzip的,oauth2.0的等,大家可以通過如下連結檢視第三方的中間清單:
https://github.com/urfave/negroni#third-party-middleware
小結
到這裡,Negroni這個中間件分析完了,可能還有一些沒有分析的,比如
With
方法,
Run
方法,他們都是比較簡單的,大家看下就可以了。
還有一個比較重要的是
NewResponseWriter
這個函數,它是對我們現在的
NewResponseWriter
的包裝,增加了一些額外的資訊,這個知識點我在這個篇 Go語言經典庫使用分析(四)| Gorilla Handlers 源代碼實作分析 文章裡詳細介紹過,就不再重複介紹了。
HTTP的中間件,就是一個HTTP的攔截器,Negroni對于該攔截器處理的更好,可以靈活控制,是否終端等,合理的使用Negroni,可以讓你事半功倍。
Go語言經典庫使用分析,未完待續,歡迎掃碼關注公衆号 flysnow_org
或者網站http://www.flysnow.org/,第一時間看後續系列。覺得有幫助的話,順手分享到朋友圈吧,感謝支援。