1. 程式人生 > 實用技巧 >在 Go 中編寫令人愉快的 HTTP 中介軟體

在 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 行程式碼(不包括註釋)的情況下,我們就能夠構建一個很好的中介軟體庫。它幾乎是簡簡單單的,但是這幾行連貫的抽象實在是太棒了。它使我們能夠毫不費力地編寫一些漂亮的中介軟體鏈。希望這幾行程式碼也能激發您的中介軟體體驗。