iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 19
0

middleware

使用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"

Custom Middleware

如果想使用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 external middleware

參數綁定

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/)

https://ithelp.ithome.com.tw/upload/images/20200925/2012951502be8yJ6IE.jpg


上一篇
[DAY18]Gin-目前Golang http框架中的最速傳說
下一篇
[DAY20]GO gRPC初體驗
系列文
欸你這週GO了嘛30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言