Go語言中間件使用方法以及原理解析


什么是中間件?
在網絡世界中,中間件針對客戶端發起的網絡請求而執行的代碼,並且將請求鏈接到下一個中間件,最終到達目標處理函數。當您希望對多個不同請求做一些共享的操作,中間件是很有用的。例如對網絡請求進行身份驗證、性能日志記錄和數據收集。
舉一個例子,假如我們有一個服務返回用戶提交的消息列表,另一個服務是返回關於該用戶的個人敏感信息。只有當用戶登錄到服務后,才能訪問這兩個服務。此外,我們還希望記錄客戶端向服務器發出的每個請求的執行時間。身份驗證和日志記錄的代碼可以與控制器一起編寫,並復制到兩個服務即可。但是這個例子很容易被分成四個不同的包來管理。一個記錄請求數據(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{})

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM