使用gin.Default()時,會預載Recovery和Logger二個middleware,
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
如果不需要預載Recovery和Logger的話,可以直接使用gin.New(),以下範例為不預載middleware,自行載入全域與group middleware
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.New()
//全域middleware
router.Use(gin.Logger())
g1 := router.Group("/v1").Use(middleware1)
//By Group設定middleware
g1.GET("/getting", func(c *gin.Context) {
fmt.Println("doing v1 getting")
c.JSON(http.StatusOK, gin.H{"data": "v1 getting"})
})
//By Group設定middleware
g2 := router.Group("/v2").Use(middleware2)
g2.GET("/getting", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": "v2 getting"})
})
router.Run()
}
func middleware1(c *gin.Context) {
fmt.Println("exec middleware1")
//c.Next() 執行middleware後面接的function,執行完後再回到middleware繼續執行下去
c.Next()
fmt.Println("after exec middleware1")
}
func middleware2(c *gin.Context) {
fmt.Println("exec middleware2")
//c.Abort()停止執行後面的hanlder,可以用來做auth
c.Abort()
c.JSON(200, gin.H{"msg": "i'm fail..."})
}
執行結果
//來自v2 Group的middleware2
exec middleware2
//因為middleware的c.Next() 關係,執行handler
doing v2 getting
//來自全域middleware
after exec middleware2
[GIN] 2020/09/24 - 16:43:25 | 200 | 130.404µs | 127.0.0.1 | GET "/v2/getting"
//來自v1 Group的middleware1
exec middleware1
//因為middleware的c.Next() 關係,執行handler
doing v1 getting
//來自全域middleware
after exec middleware1
[GIN] 2020/09/24 - 16:43:30 | 200 | 44.094µs | 127.0.0.1 | GET "/v1/getting"
如果想使用logrus當成default的logger呢,可以透過自定義一個loggermiddleware去log所有的request,
package main
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"time"
"github.com/gin-gonic/gin"
logger "github.com/sirupsen/logrus"
)
func main() {
gin.DefaultWriter = ioutil.Discard
router := gin.New()
//全域middleware,使用自定義logger
router.Use(MiddlewareLogger())
g1 := router.Group("/v1").Use(middleware1)
//By Group設定middleware
g1.GET("/getting", func(c *gin.Context) {
fmt.Println("doing v1 getting")
c.JSON(http.StatusOK, gin.H{"data": "v1 getting"})
})
//By Group設定middleware
g2 := router.Group("/v2").Use(middleware2)
g2.GET("/getting", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": "v2 getting"})
})
router.Run()
}
func middleware1(c *gin.Context) {
fmt.Println("exec middleware1")
c.Next()
fmt.Println("after exec middleware1")
}
func middleware2(c *gin.Context) {
fmt.Println("exec middleware2")
c.Abort()
c.JSON(200, gin.H{"msg": "i'm fail..."})
}
func MiddlewareLogger() gin.HandlerFunc {
return func(c *gin.Context) {
// 開始時間
startTime := time.Now()
// 處理請求
c.Next()
// 結束時間
endTime := time.Now()
// 執行時間
latencyTime := endTime.Sub(startTime)
// 請求方式
reqMethod := c.Request.Method
// 請求路由
reqUri := c.Request.RequestURI
// 狀態碼
statusCode := c.Writer.Status()
// 請求IP
clientIP := c.ClientIP()
logger.WithFields(map[string]interface{}{
"statusCode": statusCode,
"latencyTime": latencyTime,
"clientIP": clientIP,
"reqMethod": reqMethod,
"reqUri": reqUri,
}).Info("gg")
}
}
func init() {
logger.SetFormatter(&logger.JSONFormatter{})
logger.SetOutput(os.Stdout)
logger.SetLevel(logger.DebugLevel)
}
執行結果
{"clientIP":"127.0.0.1","latencyTime":49696,"level":"info","msg":"gg","reqMethod":"GET","reqUri":"/v1/getting","statusCode":200,"time":"2020-09-25T14:14:24+08:00"}
{"clientIP":"127.0.0.1","latencyTime":38514,"level":"info","msg":"gg","reqMethod":"GET","reqUri":"/v1/getting","statusCode":200,"time":"2020-09-25T14:14:24+08:00"}
如果想加個check header有沒有帶token,如果使用middleware+group的方式,可以做到定義path才會進行token auth,這樣子就不用每個hanlder裡面都寫token auth的邏輯
package main
import (
"fmt"
"io/ioutil"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
gin.DefaultWriter = ioutil.Discard
router := gin.New()
//全域middleware
router.Use(CheckTokenMiddleware())
g1 := router.Group("/v1").Use(middleware1)
//By Group設定middleware
g1.GET("/getting", func(c *gin.Context) {
fmt.Println("doing v1 getting")
c.JSON(http.StatusOK, gin.H{"data": "v1 getting"})
})
router.Run()
}
func middleware1(c *gin.Context) {
fmt.Println("exec middleware1")
//c.Next() 執行middleware後面接的function,執行完後再回到middleware繼續執行下去
c.Next()
fmt.Println("after exec middleware1")
}
func CheckTokenMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
//取得header的token值
token := c.GetHeader("token")
if token == "" {
//沒有token的話就不執行後面的handler
c.Abort()
//回傳錯誤值
c.JSON(http.StatusOK, map[string]string{"msg": "token empty"})
} else {
//有token,繼續執行後面的handler
fmt.Println("token checked")
c.Next()
}
}
}
執行結果
curl --location --request GET '127.0.0.1:8080/v1/getting'
//回傳值{"msg":"token empty"}
curl --location --request GET '127.0.0.1:8080/v1/getting' \
--header 'token: gg'
//回傳值{"data": "v1 getting"}
Gin 提供了參數綁定功能,可以將參數與定義的struct binding在一起,而其中又分成了
Must Bind跟Should Bind二種
1.Must Bind:binding拋出err時會回傳http 400錯誤碼
2.Should Bind:binding拋出err時就沒東西而己
區分二種bind很簡單,看前綴字有沒有should就知道是屬於那種
ShouldBindQuery:把query string binding到struct,struct裡面的tag要用form:"xxx"
ShouldBindJSON:把POST Body binding到struct,struct裡面的tag要用json:"xxx"
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
type Customer struct {
ID string `form:"id" json:"id"`
Level string `form:"level" json:"level"`
}
func main() {
route := gin.Default()
route.GET("/testing", test)
route.POST("/testing", test)
route.Run()
}
func test(c *gin.Context) {
var customer Customer
//Bind Query String
if err := c.ShouldBindQuery(&customer); err != nil {
fmt.Println("ShouldBindQuery fault", err)
}
//Bind Post Body
if err := c.ShouldBindJSON(&customer); err != nil {
fmt.Println("ShouldBindJSON fault", err)
}
fmt.Printf("customer:%+v", customer)
c.String(http.StatusOK, "OK")
}
執行結果
curl --location --request GET '127.0.0.1:8080/testing?id=gg&level=3'
//customer:{ID:gg Level:3}
curl --location --request POST '127.0.0.1:8080/testing' \
--header 'Content-Type: application/json' \
--data-raw '{
"id":"AA",
"level":"3"
}'
//customer:{ID:gg Level:3}
除非了參數綁定外,gin也內建了validator,只要透過struct裡面的tag定義進行資料驗證
Gin Validator
//struct裡面加入了binding:"required" tag,表示為必輸值,如果沒帶值進行binding的話,會拋出錯誤
type Customer struct {
ID string `form:"id" json:"id" binding:"required"`
Level string `form:"level" json:"level" binding:"required"`
}
//錯誤訊息:ShouldBindQuery fault Key: 'Customer.ID' Error:Field validation for 'ID' failed on the 'required' tag
Key: 'Customer.Level' Error:Field validation for 'Level' failed on the 'required' tag
以上只是gin的簡單介紹,gin還有請多東西可以挖堀,可以至[Gin官網]看看教學文章(https://gin-gonic.com/)