原文:http://www.ttlsa.com/golang/gin-middleware-example/
翻譯:devabel
我最近一段時(shí)間一直使用Go的Gin web框架開發(fā)一些小型項(xiàng)目,迄今為止它的表現(xiàn)一直很棒。Gin因其簡單性和與默認(rèn)net/http庫的兼容性而吸引了我,并且與Sinatra相似, Sinatra是一種簡約的Ruby Ruby框架。到目前為止,我已經(jīng)寫了幾個(gè)由Gin驅(qū)動的開源項(xiàng)目:
pgweb - PostgreSQL的WEB界面
omxremote - 用于Raspberry Pi omxplayer的GUI和API
envd - 通過HTTP為環(huán)境變量提供服務(wù)的API
hipache-API - HTTP API的Hipache
盡管這些項(xiàng)目中的大多數(shù)都非常簡單,但我開始更多地探索如何將我的一些使用Sinatra經(jīng)驗(yàn)帶入Go。我特別感興趣的是如何編寫中間件處理程序。你可以查看Gin的文檔,有幾個(gè)小例子。
這是基準(zhǔn)應(yīng)用程序:
package main
import(
"github.com/gin-gonic/gin"
)
func GetDummyEndpoint(c *gin.Context) {
resp := map[string]string{"hello":"world"}
c.JSON(200, resp)
}
func main() {
api := gin.Default()
api.GET("/dummy", GetDummyEndpoint)
api.Run(":5000")
}
現(xiàn)在,讓我們添加一些中間件:
func DummyMiddleware(c *gin.Context) {
fmt.Println("Im a dummy!")
// Pass on to the next-in-chain
c.Next()
}
func main() {
// Insert this middleware definition before any routes
api.Use(DummyMiddleware)
// ... more code
}
在上面的例子中調(diào)用了c.Next(),這意味著在我們的中間件完成執(zhí)行后,我們可以將請求處理程序傳遞給鏈中的下一個(gè)func。正如你看到的,中間件功能與常規(guī)端點(diǎn)功能沒有區(qū)別,因?yàn)樗鼈冎挥幸粋€(gè)參數(shù)gin.Context。但是,還有另一種定義中間件*功能的方式,就像這樣:
func DummyMiddleware() gin.HandlerFunc {
// Do some initialization logic here
// Foo()
return func(c *gin.Context) {
c.Next()
}
}
func main() {
// ...
api.Use(DummyMiddleware())
// ...
}
這兩種定義中間件功能的方式之間的區(qū)別在于,您可以在稍后的示例中執(zhí)行一些初始化邏輯。假設(shè)你需要從第三方服務(wù)中獲取一些數(shù)據(jù),但是你不能在每個(gè)請求的基礎(chǔ)上這樣做。當(dāng)中間件 被加載到請求鏈中時(shí),無論您在return語句之前定義的內(nèi)容(Foo()例如)將只執(zhí)行一次。如果您想要進(jìn)行條件檢查,比如返回一個(gè)中間件函數(shù)(如果存在一個(gè)頭)或者另一個(gè)中間件函數(shù)(如果不存在),這可能很有用。讓我們來看看例子!
Api認(rèn)證中間件
如果你正在用杜松子建立一個(gè)API ,你可能會想在你的應(yīng)用程序中添加一些認(rèn)證機(jī)制。最簡單的解決方案是檢查客戶端是否提供了額外的url參數(shù),如api_token。然后,應(yīng)該在每個(gè)請求之前對其進(jìn)行驗(yàn)證。
func respondWithError(code, message, *gin.Context) {
resp := map[string]string{"error": message}
c.JSON(code, resp)
c.Abort(code)
}
func TokenAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.Request.FormValue("api_token")
if token == "" {
respondWithError(401, "API token required", c)
return
}
if token != os.Getenv("API_TOKEN") {
respondWithError(401, "Invalid API token", c)
return
}
c.Next()
}
}
上面的例子將檢查api_token每個(gè)請求中是否存在參數(shù),并根據(jù)定義為API_TOKEN環(huán)境變量的值對其進(jìn)行驗(yàn)證。重要的部分是如果你需要終止請求鏈,你可以調(diào)用c.Abort。這將防止任何其他處理程序執(zhí)行。
代碼修改中間件
這種類型的中間件通常會在請求響應(yīng)中插入特殊的頭文件,以提供有關(guān)運(yùn)行應(yīng)用程序的git提交的一些信息。在Ruby世界中,git sha通常存儲在由capistrano或其他部署工具創(chuàng)建的版本目錄中REVISION或存儲在COMMIT文件中。事實(shí)上,我為此創(chuàng)建了機(jī)架中間件。
func RevisionMiddleware() gin.HandlerFunc {
// Revision file contents will be only loaded once per process
data, err := ioutil.ReadFile("REVISION")
// If we cant read file, just skip to the next request handler
// This is pretty much a NOOP middlware :)
if err != nil {
return func(c *gin.Context) {
c.Next()
}
}
// Clean up the value since it could contain line breaks
revision := strings.TrimSpace(string(data))
// Set out header value for each response
return func(c *gin.Context) {
c.Writer.Header().Set("X-Revision", revision)
c.Next()
}
}
結(jié)果你會在http響應(yīng)中得到一個(gè)新的標(biāo)題:
X-Revision: d4b371692d361869183d92d84caa5edb8835cf7d
請求ID 中間件
在API服務(wù)之后,X-Request-Id為響應(yīng)頭部注入一個(gè)特殊的頭部,可用于跟蹤傳入的請求以進(jìn)行監(jiān)視/調(diào)試。請求標(biāo)頭的值通常被格式化為UUID V4。
// ...
import github.com/satori/go.uuid
// ...
func RequestIdMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("X-Request-Id", uuid.NewV4().String())
c.Next()
}
}
在向服務(wù)端發(fā)出請求后,您會在響應(yīng)中看到一個(gè)新的頭信息,與此類似:
X-Request-Id: ea9ef5f9-107b-4a4e-9295-57d701d85a92