在 Go 中編寫令人愉快的 HTTP 中介軟體
在使用 Go 編寫複雜的服務時,您將遇到一個典型的主題是中介軟體。這個話題在網上被討論了一次又一次。本質上,中介軟體允許我們做了如下事情:
ServeHTTP
這些與 express.js中介軟體所做的工作非常類似。我們探索了各種庫,找到了接近我們想要的現有解決方案,但是他們要麼有不要的額外內容,要麼不符合我們的品位。顯然,我們可以在 express.js中介軟體的啟發下,寫出 20 行程式碼以下的更清晰的易用的 API(Installation API)
抽象
在設計抽象時,我們首先設想如何編寫中介軟體函式(下文開始稱為攔截器),答案非常明顯:
func NewElapsedTimeInterceptor() MiddlewareInterceptor {
return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
startTime := time.Now()
defer func() {
endTime := time.Now()
elapsed := endTime.Sub(startTime)
// 記錄時間消耗
}()
next(w, r)
}
}
func NewRequestIdInterceptor() MiddlewareInterceptor {
return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if r.Headers.Get("X-Request-Id") == "" {
r.Headers.Set("X-Request-Id", generateRequestId())
}
next(w, r)
}
}
它們看起來就像 http.HandlerFunc,但有一個額外的引數 next,該函式(引數)會繼續處理請求鏈。這將允許任何人像編寫類似 http.HandlerFunc的簡單函式一樣寫攔截器,它可以攔截呼叫,執行所需操作,並在需要時傳遞控制權。
接下來,我們設想如何將這些攔截器連線到 http.Handler或 http.HandlerFunc中。為此,首先要定義 MiddlewareHandlerFunc,它只是 http.HandlerFunc的一種型別。(type MiddlewareHandlerFunc http.HandlerFunc)。這將允許我們在 http.HandlerFunc棧上之上構建一個更好的 API。現在給定一個 http.HandlerFunc我們希望我們的鏈式 API 看起來像這樣:
func HomeRouter(w http.ResponseWriter, r *http.Request) {
// 處理請求
}
// ...
// 在程式某處註冊 Hanlder
chain := MiddlewareHandlerFunc(HomeRouter).
Intercept(NewElapsedTimeInterceptor()).
Intercept(NewRequestIdInterceptor())
// 像普通般註冊 HttpHandler
mux.Path("/home").HandlerFunc(http.HandlerFunc(chain))
將 http.HandlerFunc傳遞到 MiddlewareHandlerFunc,然後呼叫 Intercept方法註冊我們的Interceptor。 Interceptor的返回型別還是 MiddlewareHandlerFunc,它允許我們再次呼叫 Intercept。
使用 Intercept組合需要注意的一件重要事情是執行的順序。由於 chain(responseWriter, request)是間接呼叫最後一個攔截器,攔截器的執行是反向的,即它從尾部的攔截器一直返回到頭部的處理程式。這很有道理,因為你在攔截呼叫時,攔截器應該要在真正的請求處理器之前執行。
簡化
雖然這種反向鏈系統使抽象更加流暢,但事實證明,大多數情況下 s 我們有一個預編譯的攔截器陣列,能夠在不同的 handlers 之間重用。同樣,當我們將中介軟體鏈定義為陣列時,我們自然更願意以它們執行順序宣告它們(而不是相反的順序)。讓我們將這個陣列攔截器稱為中介軟體鏈。我們希望我們的中介軟體鏈看起來有點像:
// 呼叫鏈或中介軟體可以按下標的順序執行
middlewareChain := MiddlewareChain{
NewRequestIdInterceptor(),
NewElapsedTimeInterceptor(),
}
// 呼叫所有以 HomeRouter 結尾的中介軟體
mux.Path("/home").Handler(middlewareChain.Handler(HomeRouter))
vi設計http://www.maiqicn.com 辦公資源網站大全https://www.wode007.com
實現
一旦我們設計好抽象的概念,實現就顯得簡單多了
package middleware
import "net/http"
// MiddlewareInterceptor intercepts an HTTP handler invocation, it is passed both response writer and request
// which after interception can be passed onto the handler function.
type MiddlewareInterceptor func(http.ResponseWriter, *http.Request, http.HandlerFunc)
// MiddlewareHandlerFunc builds on top of http.HandlerFunc, and exposes API to intercept with MiddlewareInterceptor.
// This allows building complex long chains without complicated struct manipulation
type MiddlewareHandlerFunc http.HandlerFunc
// Intercept returns back a continuation that will call install middleware to intercept
// the continuation call.
func (cont MiddlewareHandlerFunc) Intercept(mw MiddlewareInterceptor) MiddlewareHandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
mw(writer, request, http.HandlerFunc(cont))
}
}
// MiddlewareChain is a collection of interceptors that will be invoked in there index order
type MiddlewareChain []MiddlewareInterceptor
// Handler allows hooking multiple middleware in single call.
func (chain MiddlewareChain) Handler(handler http.HandlerFunc) http.Handler {
curr := MiddlewareHandlerFunc(handler)
for i := len(chain) - 1; i >= 0; i-- {
mw := chain[i]
curr = curr.Intercept(mw)
}
return http.HandlerFunc(curr)
}
因此,在不到 20 行程式碼(不包括註釋)的情況下,我們就能夠構建一個很好的中介軟體庫。它幾乎是簡簡單單的,但是這幾行連貫的抽象實在是太棒了。它使我們能夠毫不費力地編寫一些漂亮的中介軟體鏈。希望這幾行程式碼也能激發您的中介軟體體驗。