Go Middleware 指南及其原理

什麼是中間件?

如果你已經熟悉中間件,只是想了解如何實踐,可以跳過這部分。

在網絡世界中,中間件針對客戶端發起的網絡請求而執行的代碼,並且將請求鏈接到下一個中間件,最終到達目標處理函數。當您希望對多個不同請求做一些共享的操作,中間件是很有用的。例如對網絡請求進行身份驗證、性能日誌記錄和數據收集。

舉一個例子,假如我們有一個服務返回用戶提交的消息列表,另一個服務是返回關於該用戶的個人敏感信息。
只有當用戶登錄到服務後,才能訪問這兩個服務。此外,我們還希望記錄客戶端向服務器發出的每個請求的執行時間。身份驗證和日誌記錄的代碼可以與控制器一起編寫,並複製到兩個服務即可。但是這個例子很容易被分成四個不同的包來管理。一個記錄請求數據(LOG),一個驗證用戶是否登錄(AUTH), 一個返回消息列表(RETURN MSG),和一個返回用戶數據(RETURN USER)。

通過將這些功能作爲中間件來編寫,我們可以很容易將請求連接起來,並在需要時在每個中間件上提前返回。例如,用戶沒有登錄就發送請求。
整個處理過程可能是如下方式:

[LOG] -> [AUTH] -> [RETURN MSG]
[LOG] -> [AUTH] -> [RETURN USER]

由於分離了 LOG 和 AUTH,我們只需要編寫代碼和測試一次就可以。

Go 中間件

在 Go 中,所有的網絡請求都通過實現 net/http 包中 Handler 接口來完成。對不太熟悉 Go 的開發人員來說,Handler 是響應請求並處理對請求參數的讀取和響應的寫入。因爲 Go 在其關鍵函數中都需要這個接口,所以圍繞它構建包或者裝飾器來擴展接口是很容易並且可靠的。

創建中間件的快速方法就是封裝 net/http.Handler.ServeHTTP 方法:

ServeHTTP(ResponseWriter, *Request)

通過添加一個外部函數,該函數接收一個 Handler 類型參數,並使用 HandlerFunc 函數來返回一個 Handler:

func middleware(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // Code for the middleware...
    next.ServeHTTP(w, r)
  })
}

上面的函數接收一個 Handler 類型的參數,該參數將請求鏈接到最終的函數上去。因爲返回值也是 Handler 類型,可以將調用串起來:

firstMiddleware(secondMiddleware(lastHandler))

僅通過這個簡單的方法,就可以很好地實現中間件。雖然不是很健壯,但是可行的。

接下來,我將使用 Negroni 這個包,使創建中間件變得更簡單。當然你也可以自己隨意的實現。Negroni 封裝了 router,併爲中間件的管理提供了功能。比如更簡單,靈活地連接中間件。

實現 - 對 Handler 的封裝

爲了儘可能的簡單,下面實現一箇中間件來記錄網絡請求的執行時間。將使用 Negroni 來實現,先創建 router 然後用 Negroni 來封裝:

package main

import (
    "fmt"
    "net/http"

    "github.com/gorilla/mux"
    "github.com/urfave/negroni"
)

func main() {
    router := mux.NewRouter()
    router.
        Methods("GET").
        Path("/").
        HandlerFunc(endpointHandler)

    n := negroni.New()
    n.UseHandler(router)

    err := http.ListenAndServe(":8080", n)
    if err != nil {
        panic(err)
    }
}

func endpointHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("Endpoint handler called")
}

如上面代碼所示,先用 gorilla/mux 包創建 router,然後註冊一個 Get 服務。並且創建一個 Negroni 實例,將 router 作爲參數傳入 UserHanler 函數。因爲 Negroni 實現了 Handler 接口,可以像使用 router 那樣來使用它。運行以上代碼併發起 Get 請求(curl localhost:8080/),可以觀察到 “Endpoint handler called” 內容返回給客戶端。

中間件的實現

下面繼續創建中間件,如前所述,中間件就是一個 Handler。因此,我們需要做的就是創建一個結構體來實現 Handler 接口。這裏我們使用 Negroni,我們只需要實現它的 Handler 接口即可。

如下所示,和 net/http.Hanler 接口類似,除了它還支持與下一個參數鏈接:

type Handler interface {
    ServeHTTP(
        rw http.ResponseWriter, 
        r *http.Request, 
        next http.HandlerFunc,
)
}

讓我們創建一個簡單的打印日誌程序:

package mw

import (
    "fmt"
    "net/http"
    "time"
)

type Logger struct{}

func (*Logger) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
    fmt.Println("The logger middleware is executing!")
    t := time.Now()
    next.ServeHTTP(w, r)
    fmt.Printf("Execution time: %s \n", time.Now().Sub(t).String())
}

上面的代碼展示瞭如何通過實現 Negroni.Handler 來創建一個簡單的中間件。對請求的執行時間進行記錄。最後,繼續執行下一個 Handler,它可能是另一箇中間件或是最終的處理函數。在該調用完成執行後,記錄總的執行時間。

將上面日誌中間件添加到 router 上,看看執行時間:

package main

import (
    "fmt"
    "net/http"

    "github.com/gorilla/mux"
    "github.com/johan-lejdung/go-microservice-middleware-guide/mw"
    "github.com/urfave/negroni"
)

func main() {
    router := mux.NewRouter()
    router.
        Methods("GET").
        Path("/").
        HandlerFunc(endpointHandler)

    n := negroni.New()
    n.Use(&mw.Logger{})
    n.UseHandler(router)

    err := http.ListenAndServe(":8080", n)
    if err != nil {
        panic(err)
    }
}

func endpointHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("Endpoint handler called")
}

我們只是使用 n.Use(&mw.Logger{}),告訴 Negroni 調用創建的 Logger 中間件,中間件會按照添加順序執行。執行上面的代碼就會得到如下日誌:

The logger middleware is executing!
Endpoint handler called
Execution time: 25.146µs

第一行和第三行是從中間件生成的,而第二行是在函數 endpoint Handler 中生成的。我們成功地實現了中間件。

讓我們考慮一下,我們還有一個身份驗證中間件 (AuthMW),這個 AuthMW 將確保未經授權的請求無法到達最終執行函數。要將它添加到鏈中,我們只需執行以下操作:

n.Use(&mw.Logger{})
n.Use(&mw.Auth{})

我們已經創建了中間件鏈的第一部分。上面的代碼將在繼續訪問 AUTH 中間件之前執行 Logger。以同樣的方式,你可以爲單個路由器路徑創建一箇中間件鏈,如果我們將這兩種連接中間件的方法結合起來,我們便能夠獲得一些非常強大的工具去創建我們的路由和我們想要它們執行的代碼!

完整代碼地址 https://github.com/johan-lejdung/go-microservice-middleware-guide

轉自:

jianshu.com/p/ec9a67e7f7a7

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/DzoXSM5irW-2EJI05j14FQ