用 20 行代碼寫出清晰易用的 Go 中間件 API
在使用 Go 編寫復(fù)雜的服務(wù)時(shí),您將遇到一個(gè)典型的主題是中間件。這個(gè)話題在網(wǎng)上被討論了一次又一次。本質(zhì)上,中間件允許我們做了如下事情:
- 攔截 ServeHTTP 調(diào)用,執(zhí)行任意代碼
- 對(duì)調(diào)用鏈(Continuation Chain) 上的請(qǐng)求/響應(yīng)流進(jìn)行更改
- 打斷中間件鏈,或繼續(xù)下一個(gè)中間件攔截器并最終到達(dá)真正的請(qǐng)求處理器
這些與 express.js 中間件所做的工作非常類似。我們探索了各種庫(kù),找到了接近我們想要的現(xiàn)有解決方案,但是他們要么有不要的額外內(nèi)容,要么不符合我們的品位。顯然,我們可以在 express.js 中間件的啟發(fā)下,寫出 20 行代碼以下的更清晰的易用的 API(Installation API)
抽象
在設(shè)計(jì)抽象時(shí),我們首先設(shè)想如何編寫中間件函數(shù)(下文開始稱為攔截器),答案非常明顯:
- 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)
- // 記錄時(shí)間消耗
- }()
- 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,但有一個(gè)額外的參數(shù) next,該函數(shù)(參數(shù))會(huì)繼續(xù)處理請(qǐng)求鏈。這將允許任何人像編寫類似 http.HandlerFunc 的簡(jiǎn)單函數(shù)一樣寫攔截器,它可以攔截調(diào)用,執(zhí)行所需操作,并在需要時(shí)傳遞控制權(quán)。
接下來,我們?cè)O(shè)想如何將這些攔截器連接到 http.Handler 或 http.HandlerFunc 中。為此,首先要定義 MiddlewareHandlerFunc,它只是 http.HandlerFunc 的一種類型。(type MiddlewareHandlerFunc http.HandlerFunc)。這將允許我們?cè)?http.HandlerFunc 棧上之上構(gòu)建一個(gè)更好的 API?,F(xiàn)在給定一個(gè) http.HandlerFunc 我們希望我們的鏈?zhǔn)?API 看起來像這樣:
- func HomeRouter(w http.ResponseWriter, r *http.Request) {
- // 處理請(qǐng)求
- }
- // ...
- // 在程序某處注冊(cè) Hanlder
- chain := MiddlewareHandlerFunc(HomeRouter).
- Intercept(NewElapsedTimeInterceptor()).
- Intercept(NewRequestIdInterceptor())
- // 像普通般注冊(cè) HttpHandler
- mux.Path("/home").HandlerFunc(http.HandlerFunc(chain))
將 http.HandlerFunc 傳遞到 MiddlewareHandlerFunc,然后調(diào)用 Intercept 方法注冊(cè)我們的 Interceptor。Interceptor 的返回類型還是 MiddlewareHandlerFunc,它允許我們?cè)俅握{(diào)用 Intercept。
使用 Intercept 組合需要注意的一件重要事情是執(zhí)行的順序。由于 chain(responseWriter, request)是間接調(diào)用最后一個(gè)攔截器,攔截器的執(zhí)行是反向的,即它從尾部的攔截器一直返回到頭部的處理程序。這很有道理,因?yàn)槟阍跀r截調(diào)用時(shí),攔截器應(yīng)該要在真正的請(qǐng)求處理器之前執(zhí)行。
簡(jiǎn)化
雖然這種反向鏈系統(tǒng)使抽象更加流暢,但事實(shí)證明,大多數(shù)情況下 s 我們有一個(gè)預(yù)編譯的攔截器數(shù)組,能夠在不同的 handlers 之間重用。同樣,當(dāng)我們將中間件鏈定義為數(shù)組時(shí),我們自然更愿意以它們執(zhí)行順序聲明它們(而不是相反的順序)。讓我們將這個(gè)數(shù)組攔截器稱為中間件鏈。我們希望我們的中間件鏈看起來有點(diǎn)像:
- // 調(diào)用鏈或中間件可以按下標(biāo)的順序執(zhí)行
- middlewareChain := MiddlewareChain{
- NewRequestIdInterceptor(),
- NewElapsedTimeInterceptor(),
- }
- // 調(diào)用所有以 HomeRouter 結(jié)尾的中間件
- mux.Path("/home").Handler(middlewareChain.Handler(HomeRouter))
實(shí)現(xiàn)
一旦我們?cè)O(shè)計(jì)好抽象的概念,實(shí)現(xiàn)就顯得簡(jiǎn)單多了
- 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 行代碼(不包括注釋)的情況下,我們就能夠構(gòu)建一個(gè)很好的中間件庫(kù)。它幾乎是簡(jiǎn)簡(jiǎn)單單的,但是這幾行連貫的抽象實(shí)在是太棒了。它使我們能夠毫不費(fèi)力地編寫一些漂亮的中間件鏈。希望這幾行代碼也能激發(fā)您的中間件體驗(yàn)。