Gin-中介軟體:Gin框架的作者為開發者們提供的一種機制,可以讓開發者自定義請求執行的Hook函式,中介軟體函式(或者叫Hook函式、鉤子函式)適合處理很多重複的操作的場景(比如登入認證,許可權校驗,資料分頁,耗時統計,記錄日誌等) ,如果僅僅是一個頁面的獨有的邏輯直接放到對應的路由下的Handler函式處理即可,
定義中介軟體
Gin的中介軟體必須是gin.HandlerFunc型別(就是包含請求上下文引數*gin.Context的函式)
例如:下面的indexHandler就是一箇中介軟體:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func indexHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
func main() {
r := gin.Default()
r.GET("/index", indexHandler)
r.Run(":9090")
}
再比如,我們還可以這樣定義一箇中介軟體:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func indexHandler(c *gin.Context) {
fmt.Println("this is index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
//定義一箇中介軟體m1
func m1(c *gin.Context) {
fmt.Println("this is middle-ware m1~")
}
func main() {
r := gin.Default()
//GET(relativePath string, handlers ...HandlerFunc)
r.GET("/index", m1, indexHandler)
r.Run(":9090")
}
c.Next()
但上面這樣寫中介軟體並沒有什麼實際意義,我們可以嘗試寫一個程式計時的功能,這裡介紹一下,c.Next()可以呼叫後續的處理函式:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func indexHandler(c *gin.Context) {
fmt.Println("this is index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
//定義一箇中介軟體m1
func m1(c *gin.Context) {
fmt.Println("this is middle-ware m1~")
start := time.Now()
c.Next() //呼叫後續的處理函式
//c.Abort() //組織呼叫後續的處理函式
cost := time.Since(start)
fmt.Printf("cost: %v\n ", cost)
fmt.Println("m1 is done")
}
func main() {
r := gin.Default()
//GET(relativePath string, handlers ...HandlerFunc)
r.GET("/index", m1, indexHandler)
r.Run(":9090")
}
執行結果如下:
r.Use()
當然,中介軟體的設計初衷是為了更多的複用,比如使用Use(middle_ware...)函式進行全域性註冊,比如,我有幾個路由,想在訪問這幾個路由的時候都呼叫m1計時器的功能:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func indexHandler(c *gin.Context) {
fmt.Println("this is index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
//定義一箇中介軟體m1
func m1(c *gin.Context) {
fmt.Println("this is middle-ware m1~")
start := time.Now()
c.Next() //呼叫後續的處理函式
//c.Abort() //組織呼叫後續的處理函式
cost := time.Since(start)
fmt.Printf("cost: %v\n ", cost)
fmt.Println("m1 is done")
}
func main() {
r := gin.Default()
//全域性註冊中介軟體
r.Use(m1)
//GET(relativePath string, handlers ...HandlerFunc)
r.GET("/index", indexHandler)
r.GET("/game", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop",
})
})
r.GET("/food", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "food",
})
})
r.GET("/ad", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "ad",
})
})
r.Run(":9090")
}
執行結果如下:
c.Abort()
這裡介紹一下,c.Abort()可以剝奪所有後續的處理函式執行的權利,直接跳過去,例子如下:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func indexHandler(c *gin.Context) {
fmt.Println("this is index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
//定義一箇中介軟體m1
func m1(c *gin.Context) {
fmt.Println("this is middle-ware m1~")
start := time.Now()
c.Next() //呼叫後續的處理函式
//c.Abort() //組織呼叫後續的處理函式
cost := time.Since(start)
fmt.Printf("cost: %v\n ", cost)
fmt.Println("m1 is done")
}
//定義一箇中介軟體m2
func m2(c *gin.Context) {
fmt.Println("this is middle-ware m2~")
c.Abort() //組織呼叫後續的處理函式
fmt.Println("m2 is done")
}
func main() {
r := gin.Default()
//全域性註冊中介軟體
r.Use(m1, m2)
//GET(relativePath string, handlers ...HandlerFunc)
r.GET("/index", indexHandler)
r.GET("/game", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop",
})
})
r.GET("/food", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "food",
})
})
r.GET("/ad", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "ad",
})
})
r.Run(":9090")
}
執行結果如下:
登入鑑權的中介軟體
再比如,我們想寫一個登入鑑權的中介軟體,虛擬碼就可以寫成:
//定義一個登入中介軟體
func authMiddleware(c *gin.Context) {
//if 是登入使用者
// c.Next()
//else
// c.Abort()
}
不過,為了更方便進行一些準備工作(比如連線資料庫等),通常我們更加推薦將中介軟體寫成閉包的形式:
//定義一個登入中介軟體
func authMiddleware(doChck bool) gin.HandlerFunc {
//寫成閉包的形式,就可以在這裡進行資料庫的連線,或其他的準備工作
return func(c *gin.Context) { //這裡存放校驗引數的邏輯
if doChck{
//if 是登入使用者
// c.Next()
//else
// c.Abort()
} else {
c.Next()
}
}
}
將這部分程式碼納入整體程式碼如下:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func indexHandler(c *gin.Context) {
fmt.Println("this is index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
//定義一箇中介軟體m1
func m1(c *gin.Context) {
fmt.Println("this is middle-ware m1~")
start := time.Now()
c.Next() //呼叫後續的處理函式
//c.Abort() //組織呼叫後續的處理函式
cost := time.Since(start)
fmt.Printf("cost: %v\n ", cost)
fmt.Println("m1 is done")
}
//定義一箇中介軟體m2
func m2(c *gin.Context) {
fmt.Println("this is middle-ware m2~")
c.Abort() //組織呼叫後續的處理函式
fmt.Println("m2 is done")
}
//定義一個登入中介軟體
func authMiddleware(doChck bool) gin.HandlerFunc {
//寫成閉包的形式,就可以在這裡進行資料庫的連線,或其他的準備工作
return func(c *gin.Context) { //這裡存放校驗引數的邏輯
if doChck {
//if 是登入使用者
// c.Next()
//else
// c.Abort()
} else {
c.Next()
}
}
}
func main() {
r := gin.Default()
//全域性註冊中介軟體
r.Use(m1, m2, authMiddleware(true))
//GET(relativePath string, handlers ...HandlerFunc)
r.GET("/index", indexHandler)
r.GET("/game", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop",
})
})
r.GET("/food", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "food",
})
})
r.GET("/ad", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "ad",
})
})
r.Run(":9090")
}
當然,路由組也可以進行全域性註冊,例如:
billGroup := r.Group("/bill")
billGroup.Use(authMiddleware(true))
{
billGroup.GET("/index1", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "lokaloka1"})
})
billGroup.GET("/index2", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "lokaloka2"})
})
billGroup.GET("/index3", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "lokaloka3"})
})
}
中介軟體之間通訊 c.Set()與c.Get()
比如在中介軟體m2中設定一個鍵值對,想在後面的indexHandler中介軟體中根據key取得value,可以這樣寫:
//定義一箇中介軟體m2
func m2(c *gin.Context) {
fmt.Println("this is middle-ware m2~")
//c.Abort() //組織呼叫後續的處理函式
c.Set("name", "lokays")
fmt.Println("m2 is done")
}
func indexHandler(c *gin.Context) {
fmt.Println("this is index")
name, ok := c.Get("name")
if !ok {
name = "匿名使用者"
}
c.JSON(http.StatusOK, gin.H{
"msg": name,
})
}
注意
? 注意:gin.Default()
會自動載入Logger中介軟體和Recovery中介軟體:
- Logger中介軟體會將日誌寫入gin.DefaultWriter,無論是否配置了GIN_Mode
- Recovery中介軟體會Recover所有的panic,如果有panic會寫入500響應碼
所以,如果不想使用任何中介軟體, 可以用gin.New()新建一個路由。
- 在中介軟體或者Handler中啟動新的goroutine的時候,不能使用上下文的c *gin.Context,只能使用只讀副本c.Copy()
參考:bilibili