Gin框架中間件
Gin框架允許開發者在處理請求的過程中,加入用戶自己的鈎子(Hook)函數。這個鈎子函數就叫中間件,中間件適合處理一些公共的業務邏輯,比如登錄認證、權限校驗、數據分頁、記錄日志、耗時統計等。
一、定義中間件
Gin中的中間件必須是一個gin.HandlerFunc
類型。例如我們像下面的代碼一樣定義一個統計請求耗時的中間件。
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func index(c *gin.Context) {
fmt.Println("index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
// 定義一個中間件m1統計請求的耗時時間
func m1(c *gin.Context) {
fmt.Println("m1 in ....")
// 計算請求時間
start :=time.Now()
c.Next() // 調用后續處理的函數
//c.Abort() // 阻止調用后續處理的函數
end := time.Since(start)
fmt.Printf("消耗時間time:%v\n", end)
fmt.Println("m1 out ....")
}
func main() {
r := gin.Default()
r.GET("/index", m1, index)
r.Run(":9999")
}
二、定義局部中間件
在每次請求中添加中間件
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func index(c *gin.Context) {
fmt.Println("index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
// 定義一個中間件m1統計請求的耗時時間
func m1(c *gin.Context) {
fmt.Println("m1 in ....")
// 計算請求時間
start :=time.Now()
c.Next() // 調用后續處理的函數
//c.Abort() // 阻止調用后續處理的函數
end := time.Since(start)
fmt.Printf("消耗時間time:%v\n", end)
fmt.Println("m1 out ....")
}
func main() {
r := gin.Default()
r.GET("/index", m1, index)
// 局部中間件
r.GET("/home", m1, func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg":"home",
})
})
r.GET("/add", m1, func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg":"add",
})
})
r.Run(":9999")
}
三、定義全局中間件
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func index(c *gin.Context) {
fmt.Println("index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
// 定義一個中間件m1統計請求的耗時時間
func m1(c *gin.Context) {
fmt.Println("m1 in ....")
// 計算請求時間
start := time.Now()
c.Next() // 調用后續處理的函數
//c.Abort() // 阻止調用后續處理的函數
end := time.Since(start)
fmt.Printf("消耗時間time:%v\n", end)
fmt.Println("m1 out ....")
}
func main() {
r := gin.Default()
// 全局注冊中間
r.Use(m1)
r.GET("/index", index)
// 局部中間件
r.GET("/home", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "home",
})
})
r.GET("/add", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "add",
})
})
r.Run(":9999")
}
三、定義多個中間件
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func index(c *gin.Context) {
fmt.Println("index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
// 定義一個中間件m1統計請求的耗時時間
func m1(c *gin.Context) {
fmt.Println("m1 in ....")
// 計算請求時間
start := time.Now()
c.Next() // 調用后續處理的函數
//c.Abort() // 阻止調用后續處理的函數
end := time.Since(start)
fmt.Printf("消耗時間time:%v\n", end)
fmt.Println("m1 out ....")
}
func m2(c *gin.Context) {
fmt.Println("m2 in ....")
c.Next() // 調用后續處理的函數
fmt.Println("m2 out ....")
}
func main() {
r := gin.Default()
// 全局注冊中間
r.Use(m1, m2)
r.GET("/index", index)
// 局部中間件
r.GET("/home", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "home",
})
})
r.GET("/add", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "add",
})
})
r.Run(":9999")
}
四、Abort
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func index(c *gin.Context) {
fmt.Println("index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
// 定義一個中間件m1統計請求的耗時時間
func m1(c *gin.Context) {
fmt.Println("m1 in ....")
// 計算請求時間
start := time.Now()
c.Next() // 調用后續處理的函數
end := time.Since(start)
fmt.Printf("消耗時間time:%v\n", end)
fmt.Println("m1 out ....")
}
func m2(c *gin.Context) {
fmt.Println("m2 in ....")
c.Abort() // 阻止調用后續處理的函數
return // 后面m2將不會在執行
fmt.Println("m2 out ....")
}
func main() {
r := gin.Default()
// 全局注冊中間
r.Use(m1, m2)
r.GET("/index", index)
// 局部中間件
r.GET("/home", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "home",
})
})
r.GET("/add", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "add",
})
})
r.Run(":9999")
}
中間件return語句
五、模擬認證
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func index(c *gin.Context) {
fmt.Println("index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
// 定義一個中間件m1統計請求的耗時時間
func m1(c *gin.Context) {
fmt.Println("m1 in ....")
// 計算請求時間
start := time.Now()
c.Next() // 調用后續處理的函數
//c.Abort() // 阻止調用后續處理的函數
end := time.Since(start)
fmt.Printf("消耗時間time:%v\n", end)
fmt.Println("m1 out ....")
}
func m2(c *gin.Context) {
fmt.Println("m2 in ....")
c.Next() // 調用后續處理的函數
fmt.Println("m2 out ....")
}
// 認證中間件
//func authMiddleware(c *gin.Context) {
// // 是否登錄判斷
// username := c.Query("username")
// // 判斷是否登錄用戶
// if username == "RandySun" {
//
// c.Next()
// } else {
// // 認證失敗
// c.JSON(http.StatusUnauthorized, gin.H{
// "msg": "沒有權限",
// })
// c.Abort()
// }
//}
// 通過閉包認證中間件
func authMiddleware(doCheck bool) gin.HandlerFunc {
// 連接數據庫
// 或者其他准備工作
return func(c *gin.Context) {
// 是否登錄判斷
username := c.Query("username")
// 判斷是否登錄用戶
if doCheck {
if username == "RandySun" && doCheck {
c.Next()
} else {
// 認證失敗
c.JSON(http.StatusUnauthorized, gin.H{
"msg": "沒有權限",
})
c.Abort()
}
} else {
// 放行認證
c.Next()
}
}
}
func main() {
r := gin.Default()
// 全局注冊中間
r.Use(m1, m2, authMiddleware(true))
r.GET("/index", index)
r.Run(":9999")
}
六、為路由組注冊中間件
為路由組注冊中間件有以下兩種寫法。
寫法1:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func index(c *gin.Context) {
fmt.Println("index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
// 通過閉包認證中間件
func authMiddleware(doCheck bool) gin.HandlerFunc {
// 連接數據庫
// 或者其他准備工作
return func(c *gin.Context) {
// 是否登錄判斷
username := c.Query("username")
// 判斷是否登錄用戶
if doCheck {
if username == "RandySun" && doCheck {
c.Next()
} else {
// 認證失敗
c.JSON(http.StatusUnauthorized, gin.H{
"msg": "沒有權限",
})
c.Abort()
}
} else {
// 放行認證
c.Next()
}
}
}
func main() {
r := gin.Default()
// 為路由組添加中間件
shopGroup := r.Group("/shop", authMiddleware(true))
{
shopGroup.GET("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"mgs": "shop index",
})
})
}
r.Run(":9999")
}
寫法2:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func index(c *gin.Context) {
fmt.Println("index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
// 通過閉包認證中間件
func authMiddleware(doCheck bool) gin.HandlerFunc {
// 連接數據庫
// 或者其他准備工作
return func(c *gin.Context) {
// 是否登錄判斷
username := c.Query("username")
// 判斷是否登錄用戶
if doCheck {
if username == "RandySun" && doCheck {
c.Next()
} else {
// 認證失敗
c.JSON(http.StatusUnauthorized, gin.H{
"msg": "沒有權限",
})
c.Abort()
}
} else {
// 放行認證
c.Next()
}
}
}
func main() {
r := gin.Default()
shopGroup := r.Group("/shop")
// 為路由組添加中間件
shopGroup.Use(authMiddleware(true))
{
shopGroup.GET("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"mgs": "shop index",
})
})
}
r.Run(":9999")
}
七、中間件取值
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func index(c *gin.Context) {
fmt.Println("index")
name, _ := c.Get("name")
// 跨中間件取值
fmt.Println("獲取在中間中設置的值name:", name)
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
// 定義一個中間件m1統計請求的耗時時間
func m1(c *gin.Context) {
fmt.Println("m1 in ....")
// 計算請求時間
start := time.Now()
c.Next() // 調用后續處理的函數
//c.Abort() // 阻止調用后續處理的函數
end := time.Since(start)
fmt.Printf("消耗時間time:%v\n", end)
fmt.Println("m1 out ....")
}
func m2(c *gin.Context) {
fmt.Println("m2 in ....")
// 在中間中設置值
c.Set("name", "randySun")
c.Next() // 調用后續處理的函數
fmt.Println("m2 out ....")
}
func main() {
r := gin.Default()
// 全局注冊中間
r.Use(m1, m2)
r.GET("/index", index)
r.Run(":9999")
}
八、中間件
在gin框架中,我們可以為每個路由添加任意數量的中間件。
為全局路由注冊
func main() {
// 新建一個沒有任何默認中間件的路由
r := gin.New()
// 注冊一個全局中間件
r.Use(StatCost())
r.GET("/test", func(c *gin.Context) {
name := c.MustGet("name").(string) // 從上下文取值
log.Println(name)
c.JSON(http.StatusOK, gin.H{
"message": "Hello world!",
})
})
r.Run()
}
為某個路由單獨注冊
// 給/test2路由單獨注冊中間件(可注冊多個)
r.GET("/test2", StatCost(), func(c *gin.Context) {
name := c.MustGet("name").(string) // 從上下文取值
log.Println(name)
c.JSON(http.StatusOK, gin.H{
"message": "Hello world!",
})
})
中間件注意事項
gin默認中間件
gin.Default()
默認使用了Logger
和Recovery
中間件,其中:
Logger
中間件將日志寫入gin.DefaultWriter
,即使配置了GIN_MODE=release
。Recovery
中間件會recover任何panic
。如果有panic的話,會寫入500響應碼。
如果不想使用上面兩個默認的中間件,可以使用gin.New()
新建一個沒有任何默認中間件的路由。
gin中間件中使用goroutine
當在中間件或handler
中啟動新的goroutine
時,不能使用原始的上下文(c *gin.Context),必須使用其只讀副本(c.Copy()
)。
九、運行多個服務
多個端口啟動服務,例如:
package main
import (
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
)
var (
g errgroup.Group
)
func router01() http.Handler {
e := gin.New()
e.Use(gin.Recovery())
e.GET("/", func(c *gin.Context) {
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 01",
},
)
})
return e
}
func router02() http.Handler {
e := gin.New()
e.Use(gin.Recovery())
e.GET("/", func(c *gin.Context) {
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 02",
},
)
})
return e
}
func main() {
server01 := &http.Server{
Addr: ":8080",
Handler: router01(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
server02 := &http.Server{
Addr: ":8081",
Handler: router02(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
// 借助errgroup.Group或者自行開啟兩個goroutine分別啟動兩個服務
g.Go(func() error {
return server01.ListenAndServe()
})
g.Go(func() error {
return server02.ListenAndServe()
})
if err := g.Wait(); err != nil {
log.Fatal(err)
}
}