轉,原文: http://www.guoxiaolong.cn/blog/?id=8600
-----------------
中間件Middleware
所謂中間件,就是連接上下級不同功能的函數或者軟件,通常進行一些包裹函數的行爲,爲被包裹函數提供添加一些功能或行爲。前文的HandleFunc就能把簽名爲 func(w http.ResponseWriter, r *http.Reqeust)的函數包裹成handler。這個函數也算是中間件。
這裏我們以HTTP請求的中間件爲例子,提供一個log中間件,能夠打印出每一個請求的log。
go的http中間件很簡單,只要實現一個函數簽名爲func(http.Handler) http.Handler的函數即可。http.Handler是一個接口,接口方法我們熟悉的爲serveHTTP。返回也是一個handler。因爲go中的函數也可以當成變量傳遞或者或者返回,因此也可以在中間件函數中傳遞定義好的函數,只要這個函數是一個handler即可,即實現或者被handlerFunc包裹成爲handler處理器。
func middlewareHandler(next http.Handler) http.Handler{
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
// 執行handler之前的邏輯
next.ServeHTTP(w, r)
// 執行完畢handler後的邏輯
})
}
這種方式在Elixir的Plug框架中很流行,思想偏向於函數式範式。熟悉python的朋友一定也想到了裝飾器。閒話少說,來看看go是如何實現的吧:
func loggingHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("Started %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("Comleted %s in %v", r.URL.Path, time.Since(start))
})
}
func main() {
http.Handle("/", loggingHandler(http.HandlerFunc(index)))
http.ListenAndServe(":8000", nil)
}
loggingHandler即是一箇中間件函數,將請求的和完成的時間處理。可以看見請求或go的輸出:
2016/12/04 21:18:13 Started GET /
2016/12/04 21:18:13 Comleted / in 13.365µs
2016/12/04 21:18:20 Started GET /
2016/12/04 21:18:20 Comleted / in 17.541µs
既然中間件是一種函數,並且簽名都是一樣,那麼很容易就聯想到函數一層包一層的中間件。再添加一個函數,然後修改main函數:
func hook(next http.Handler) http.Handler{
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("before hook")
next.ServeHTTP(w, r)
log.Println("after hook")
})
}
func main() {
http.Handle("/", hook(loggingHandler(http.HandlerFunc(index))))
http.ListenAndServe(":8000", nil)
}
在loggingHandler再包了一層hook,可以看到輸出爲:
2016/12/04 21:26:30 before hook
2016/12/04 21:26:30 Started GET /
2016/12/04 21:26:30 Comleted / in 14.016µs
2016/12/04 21:26:30 after hook
函數調用形成了一條鏈,可以是在這條鏈上做很多事情。當然go的寫法上,比起elixir的|>的符號,優雅性略差。
作者:dongzd
鏈接:https://www.jianshu.com/p/9b081b179e71
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
---------------------
HTTP中間件機制實現與原理 – 從零開始寫GO-API框架
大家好,很高興您能閱讀這篇文章。
最近在投稿公衆號時發現從未做過自我介紹,首先請允許我介紹一下自己。
我叫張曉亮,就職於新浪微博,Golang的忠實粉絲,平時的愛好看看書、擼擼碼,典型的程序員性格,最近喜歡上寫博客,發現不僅能從中瞭解到很多細節,也能幫助到其他人。
引言
因golang內置的net/http天生就支持http 中間件 機制,所以即使不使用開源web框架,我們也可以寫出擴展性很好的應用。
一個好的中間件有一個責任就是可插拔並且自足,這就意味着你可以在接口級別嵌入你的中間件他就能直接運行,且不會影響你的編碼方式,這不是框架,僅僅是在請求處理裏面的一層而已。
可以想象每一箇中間件都是一層洋蔥皮,其中每一箇中間件都可以改變請求響應,我們可以很自然的把不同的邏輯放到不同的洋蔥皮裏,讓代碼更符合單一原則。
你可以使用中間件做什麼?
1、重置HTTP請求路由
2、統一安全限制、信息上報
3、Header操作、http請求認證
4、屏蔽爬蟲
5、提供調試信息
6、請求日誌記錄
還有很多,可以自行發掘下
中間件通常會是一小段代碼,它可以接受一個請求,對其進行處理,每個中間件只處理一件事,完成後將其傳遞給下一層或最終處理。這樣就做到了程序的 解耦 。
如果沒有中間件我們必須在最終處理程序中來完成這個邏輯操作,這無疑會造成你現有業務邏輯的臃腫和代碼複用率不高的問題.
http中間件使用案例
慣例我們來看使用中間件的案例:
測試代碼
func Middleware Loger() koala.HandlerFunc {
return func(ctx *koala.Context) {
fmt.Printf("%+v", ctx.Req)
fmt.Println("middloger 中間件")
// fmt.Printf("%+V 進入到中間件了\n", ctx)
}
}
func main() {
app := koala.New()
app.Use(middlewareLoger())
app.Add("GET", "/ profile /xiaoliang", func(ctx *koala.Context) {
ctx.Text("profile.xiaoliang")
})
}
web請求地址: 輸出
[KOALA-DEBUG][啓動中]
Add GET /profile/xiaoliang
Add GET /profile/xiaoliang1
Add GET /member/:id
[KOALA-DEBUG]監聽端口[:8080]
[KOALA-DEBUG][服務啓動成功]
此處是中間件輸出信息
&{Method:GET URL:/profile/xiaoliang1 Proto:HTTP/1.1 ...... ctx:0xc00005c4c0}middlewareLoger 中間件
實現擴展HTTP中間件機制
以上講解了中間件的相關基礎知識,也看了使用案例,下面我們來實現一下中間件是如何嵌入到web框架中,並以此來實現各種解耦的功能。
先來看張圖
可以看看,如果我們把路由函數xiaoliang看做漢堡裏的肉餅,中間件函數看成麪包,那麼 middleware Loger包住了肉餅。
這裏可以實現很多層的中間件,爲了實現簡單,我們這裏就做一層。
擼代碼
準備工作完成,我們開始擼代碼吧。我比較喜歡這個環節_.
以下代碼邏輯,是按照實現先後順序展示
代碼位置:
type Middleware interface {}
// 定義 koala引擎結構
type Koala struct {
...代碼摺疊
// 中間件
middleware []HandlerFunc
}
// 註冊中間件
func(k *Koala) Use(m ...Middleware) {
for i := range m {
if m[i] != nil {
// 註冊中間件放入切片數組
k.middleware = append(k.middleware, warpMiddleware(m[i]))
}
}
}
處理不同中間件類型,在這裏我們就很容易擴展HTTP中間件機制了
目前僅實現request,後續會陸續擴展
func warpMiddleware(m Middleware) HandlerFunc {
// 斷言當前函數類型
switch m:= m.(type) {
case HandlerFunc:
return m
case func (*Context):
return m
default:
fmt.Printf("%+V", m)
panic("沒找到相關中間件")
}
}
next.ServHTTP中間件用戶態實現
// 實現net/http 需要的servehttp服務
func (k *Koala) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
...代碼摺疊
// 執行相關操作
ctx.Next()
...代碼摺疊
}
代碼位置:
// 首先處理中間件,然後處理路由句柄
func (c *Context) Next() {
// http中間件處理
c.middleware()
// 路由映射處理
c.koala. router ().HandlerRouter(c)
}
// 執行HTTP中間件
func (c *Context) middleware() {
for m := range c.koala.middleware{
// c http上下文傳遞給註冊的中間件函數,這時候已經拿到了用戶側web請求數據,可以進行響應的邏輯操作
c.koala.middleware[m](c)
}
}
曉亮嘚吧嘚,請聽下回分解
看到這裏大夥也發現中間件代碼部分實際上不復雜,複雜是一些概念上的知識。
作者原是phper,對於中間件的瞭解基本屬於黑洞。所以這章基礎知識講解的多了一些,一自己鞏固知識點,二讓看文章的您也可以跟着我的節奏複習一下。
文章還會陸續更新,最近在研究數據庫連接池相關知識,準備利用這個框架的輪廓,我們在寫一個關於連接池相關的包。
作者本人golang相關知識有限,如講解不正確,還希望大牛們能幫忙指正,曉亮先感謝您~