golang中很多網絡相關的庫都使用到了一種 middleware
的編程技巧,包括 rpc 和 http。但是這種技巧剛接觸很容易搞不清楚概念,在這里記錄一下我的理解。
以rpc框架 kite 為例 github地址
kite 作為一個rpc框架,提供了 middleware 的接口,保證多個 rpc 請求可以進行共同的一些配置,包括 超時,LB,日志記錄等等。具體代碼如下:
// EndPoint represent one method for calling from remote.
type EndPoint func(ctx context.Context, req interface{}) (resp interface{}, err error)
// Middleware deal with input EndPoint and output EndPoint
type Middleware func(EndPoint) EndPoint
// Chain connect middlewares into one middleware.
func Chain(outer Middleware, others ...Middleware) Middleware {
return func(next EndPoint) EndPoint {
for i := len(others) - 1; i >= 0; i-- {
next = others[i](next)
}
return outer(next)
}
}
然后是我自己寫的一個例子:
func MW1(next EndPoint) EndPoint {
return func(ctx context.Context, req interface{}) (resp interface{}, err error) {
ctx, cancel := context.WithTimeout(ctx, time.Second)
fmt.Printf("Enter MW1\n")
defer cancel()
return next(ctx, req)
}
}
func MW2(next EndPoint) EndPoint {
return func(ctx context.Context, req interface{}) (resp interface{}, err error) {
fmt.Printf("Enter MW2\n")
return next(ctx, req)
}
}
func real(ctx context.Context, req interface{}) (resp interface{}, err error) {
fmt.Printf("Enter real. req: %v\n", req)
return 1, nil
}
func main() {
var mw = Chain(MW1, MW2)
fmt.Printf("finish calling chain\n")
var resp, err = mw(real)(context.Background(), "hello")
fmt.Printf("resp: %v error: %v\n", resp, err)
}
最終的打印結果:
finish calling chain
Enter other 0: 0x10a0040
Enter MW1
Enter MW2
Enter real. req: hello
resp: 1 error: <nil>
這里的 EndPoint 和 Middleware 可以這么理解:
EndPoint 是一個處理單元,而 Middleware 可以理解為對一串處理單元的包裝,最終適配另一個 EndPoint。如圖:
其中 Middleware1 就是藍色的,它包含了兩個 EndPoint,Middleware2 是綠色的,它包含了后面兩個 EndPoint,Middleware3 是黃色的,它包含了 Middleware1 和 Middleware2 兩個合起來的處理邏輯,也就是說可以通過調用 Chain(Middleware1, Middleware2)
得到 Middleware3。
千萬不能將 EndPoint 之間的線認為是 Middleware,因為一個 Middleware 是包含了多個 EndPoint 的。同時,從編碼的角度來說,一般中間節點的 EndPoint 邏輯都會寫在 Middleware 的匿名函數里面。
最終調用一個中間件時,需要傳入一個“葉子”EndPoint,也就是上圖中的 EndPoint n,這個EndPoint一般包含了真正的處理邏輯。