Golang Gin框架中間件的用法詳解
一、中間件的基本概念
中間件是一個(gè)函數(shù),它在HTTP請(qǐng)求處理的生命周期中的某個(gè)特定點(diǎn)被調(diào)用,可以對(duì)請(qǐng)求和響應(yīng)進(jìn)行預(yù)處理或后處理。中間件的主要功能包括日志記錄、身份驗(yàn)證、權(quán)限控制、跨域資源共享(CORS)、參數(shù)處理、錯(cuò)誤處理等。
日志記錄:記錄請(qǐng)求的詳細(xì)信息,如請(qǐng)求路徑、請(qǐng)求體等。
身份驗(yàn)證:驗(yàn)證用戶的身份,如檢查令牌、Cookie等。
權(quán)限控制:確定用戶是否有權(quán)訪問(wèn)特定的資源或執(zhí)行特定的操作。
跨域資源共享(CORS):允許不同域之間的資源共享。
參數(shù)處理:提取URL中的參數(shù)或請(qǐng)求體中的數(shù)據(jù)。
錯(cuò)誤處理:捕獲并處理請(qǐng)求處理過(guò)程中的錯(cuò)誤。
在Gin框架中,中間件必須是一個(gè)gin.HandlerFunc類型的函數(shù)。中間件函數(shù)接受一個(gè)*gin.Context參數(shù),并可以選擇返回一個(gè)gin.HandlerFunc。中間件函數(shù)的典型結(jié)構(gòu)如下:
func myHandler() gin.HandlerFunc { return func(c *gin.Context) { // 可以通過(guò)c.Set在請(qǐng)求上下文中設(shè)置值,后續(xù)的處理函數(shù)能夠取到該值 c.Set("userSession", "userid-1") //c.Next() // 放行,默認(rèn)就會(huì)放行 c.Abort() // 攔截,到這里就不會(huì)往下執(zhí)行請(qǐng)求了 fmt.Println("HandlerFunc-info") } }
二、中間件的兩個(gè)專屬方法
1. ctx.Next() 繼續(xù)
在程序進(jìn)入中間件的時(shí)候需要先進(jìn)行一些處理,然后去 執(zhí)行核心業(yè)務(wù),在執(zhí)行完核心業(yè)務(wù)之后再回來(lái)執(zhí)行該中間件。
2. ctx.Abort() 中斷
在程序進(jìn)入中間件之后我們進(jìn)行了一些操作,判斷該用戶不滿足訪問(wèn)這個(gè)請(qǐng)求的條件,這個(gè)時(shí)候我們就需要終止這個(gè)請(qǐng)求,不讓其繼續(xù)執(zhí)行,這個(gè)時(shí)候就使用到了Abort
三、注冊(cè)中間件
在Gin框架中,可以通過(guò)全局注冊(cè)或單個(gè)路由中注冊(cè),路由組注冊(cè)的方式來(lái)使用中間件。
1、定義一個(gè)我們自己的 HandlerFunc 如上
2、注冊(cè)全局路由
所有的請(qǐng)求都會(huì)經(jīng)過(guò)這里來(lái)處理
全局中間件會(huì)被應(yīng)用到所有的路由上。使用r.Use()函數(shù)來(lái)注冊(cè)全局中間件。
package main import ( "fmt" "github.com/gin-gonic/gin" "log" "net/http" ) // 定義一個(gè)中間件函數(shù) func myHandler() gin.HandlerFunc { //返回一個(gè)gin.HandlerFunc函數(shù) return func(c *gin.Context) { // 可以通過(guò)c.Set在請(qǐng)求上下文中設(shè)置值,后續(xù)的處理函數(shù)能夠取到該值 // func (c *Context) Set(key string, value any) c.Set("userSession", "userid-1") //c.Next() // 放行,默認(rèn)就會(huì)放行 //c.Abort() // 攔截,到這里就不會(huì)往下執(zhí)行請(qǐng)求了 可以通過(guò)Abort做判定,控制請(qǐng)求的走向 fmt.Println("HandlerFunc-info") } } func main() { ginServer := gin.Default() // 注冊(cè) 全局的 HandlerFunc // func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes // 注意,這里面的參數(shù)是我們定義的中間件函數(shù)的執(zhí)行 ginServer.Use(myHandler()) // 接收請(qǐng)求 ginServer.GET("/test", func(c *gin.Context) { // 從上下文取值 拿到我們?cè)谥虚g件中設(shè)置的值 name := c.MustGet("userSession").(string) log.Println(name) c.JSON(http.StatusOK, gin.H{ "myname": name, }) }) //再加個(gè)post請(qǐng)求,驗(yàn)證全局中間件 ginServer.POST("/test", func(c *gin.Context) { namePost := c.MustGet("userSession").(string) log.Println(namePost) c.JSON(http.StatusOK, gin.H{ "myname": namePost, }) }) err := ginServer.Run() if err != nil { return } }
get請(qǐng)求拿到數(shù)據(jù)
post請(qǐng)求也拿到數(shù)據(jù)
3、為某個(gè)路由單獨(dú)注冊(cè)
為單個(gè)路由注冊(cè)的中間件,只能在該路由中生效
package main import ( "fmt" "github.com/gin-gonic/gin" "log" "net/http" ) // 定義一個(gè)中間件函數(shù) func myHandler() gin.HandlerFunc { //返回一個(gè)gin.HandlerFunc函數(shù) return func(c *gin.Context) { // 可以通過(guò)c.Set在請(qǐng)求上下文中設(shè)置值,后續(xù)的處理函數(shù)能夠取到該值 // func (c *Context) Set(key string, value any) c.Set("userSession", "userid-1") //c.Next() // 放行,默認(rèn)就會(huì)放行 //c.Abort() // 攔截,到這里就不會(huì)往下執(zhí)行請(qǐng)求了 可以通過(guò)Abort做判定,控制請(qǐng)求的走向 fmt.Println("HandlerFunc-info") } } func main() { ginServer := gin.Default() // 單路由注冊(cè)中間件 ,只能在該路由中生效 ginServer.GET("/test", myHandler(), func(c *gin.Context) { // 從上下文取值 拿到我們?cè)谥虚g件中設(shè)置的值 name := c.MustGet("userSession").(string) log.Println(name) c.JSON(http.StatusOK, gin.H{ "myname": name, }) }) //再加個(gè)post請(qǐng)求,post請(qǐng)求沒(méi)有注冊(cè)中間件,中間件在該路由中不生效 ginServer.POST("/test", func(c *gin.Context) { //當(dāng)沒(méi)有在該路由中注冊(cè)中間件時(shí),獲取中間件中的變量值,獲取不到會(huì)報(bào)panic namePost := c.MustGet("userSession").(string) log.Println(namePost) c.JSON(http.StatusOK, gin.H{ "myname": namePost, }) }) err := ginServer.Run() if err != nil { return } }
當(dāng)沒(méi)有在該路由中注冊(cè)中間件時(shí),獲取中間件中的變量值,獲取不到會(huì)報(bào)panic
拿不到值報(bào)異常
4、為路由組注冊(cè)中間件
路由組中間件只會(huì)被應(yīng)用到該路由組下的路由上。通過(guò)創(chuàng)建一個(gè)路由組,并使用v1.Use()函數(shù)為該路由組注冊(cè)中間件。
package main import ( "fmt" "github.com/gin-gonic/gin" "log" "net/http" ) // 定義一個(gè)中間件函數(shù) func myHandler() gin.HandlerFunc { //返回一個(gè)gin.HandlerFunc函數(shù) return func(c *gin.Context) { // 可以通過(guò)c.Set在請(qǐng)求上下文中設(shè)置值,后續(xù)的處理函數(shù)能夠取到該值 // func (c *Context) Set(key string, value any) c.Set("userSession", "userid-1") //c.Next() // 放行,默認(rèn)就會(huì)放行 //c.Abort() // 攔截,到這里就不會(huì)往下執(zhí)行請(qǐng)求了 可以通過(guò)Abort做判定,控制請(qǐng)求的走向 fmt.Println("HandlerFunc-info") } } func main() { ginServer := gin.Default() //創(chuàng)建路由組 v1 := ginServer.Group("/v1") // 創(chuàng)建一個(gè)名為/v1的路由組 //路由組應(yīng)用中間件 v1.Use(myHandler()) // 路由組中間件 { v1.GET("/test", func(c *gin.Context) { // 從上下文取值 拿到我們?cè)谥虚g件中設(shè)置的值 name := c.MustGet("userSession").(string) log.Println(name) c.JSON(http.StatusOK, gin.H{ "myname": name, }) }) //再加個(gè)post請(qǐng)求,post請(qǐng)求沒(méi)有注冊(cè)中間件,中間件在該路由中不生效 v1.POST("/test", func(c *gin.Context) { //當(dāng)沒(méi)有在該路由中注冊(cè)中間件時(shí),獲取中間件中的變量值,獲取不到會(huì)報(bào)panic namePost := c.MustGet("userSession").(string) log.Println(namePost) c.JSON(http.StatusOK, gin.H{ "myname": namePost, }) }) } err := ginServer.Run() if err != nil { return } }
在上述代碼中,我們創(chuàng)建了一個(gè)名為/v1的路由組,并為這個(gè)路由組注冊(cè)了一個(gè)中間件。這個(gè)中間件只會(huì)被應(yīng)用到/v1下的路由上。
路由組中間件也可以直接應(yīng)用在Group方法中
v1 := r.Group("/test", myHandler()) { shopGroup.GET("/index", func(c *gin.Context) {...}) ... }
5、gin默認(rèn)中間件
gin.Default()
默認(rèn)使用了Logger
和Recovery
中間件,其中:
Logger
中間件將日志寫入gin.DefaultWriter
,即使配置了GIN_MODE=release
。Recovery
中間件會(huì)recover任何panic
。如果有panic的話,會(huì)寫入500響應(yīng)碼。
如果不想使用上面兩個(gè)默認(rèn)的中間件,可以使用gin.New()
新建一個(gè)沒(méi)有任何默認(rèn)中間件的路由。
三、實(shí)際應(yīng)用案例
以下將通過(guò)幾個(gè)實(shí)際案例來(lái)演示Gin框架中間件的用法。
1. 日志記錄中間件
日志記錄中間件用于記錄請(qǐng)求的詳細(xì)信息,如請(qǐng)求路徑、請(qǐng)求體、響應(yīng)狀態(tài)碼等。
package middleware import ( "github.com/gin-gonic/gin" "log" "time" ) // LoggerMiddleware 是一個(gè)日志記錄中間件 func LoggerMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 記錄請(qǐng)求開始時(shí)的信息 startTime := time.Now() // 調(diào)用后續(xù)的處理函數(shù) c.Next() // 記錄請(qǐng)求結(jié)束時(shí)的信息 endTime := time.Now() latency := endTime.Sub(startTime) // 記錄請(qǐng)求的IP地址和端口號(hào) ipAddress := c.ClientIP() // 記錄請(qǐng)求的URL requestUrl := c.Request.URL.Path // 記錄請(qǐng)求的方法 httpMethod := c.Request.Method // 記錄請(qǐng)求的狀態(tài)碼 statusCode := c.Writer.Status() // 記錄日志信息 log.Printf("Request from %s to %s took %s with method %s and status code %d\n", ipAddress, requestUrl, latency, httpMethod, statusCode) } }
在main.go中使用該中間件:
package main import ( "github.com/gin-gonic/gin" "jingtian/jt_gin/middleware" "net/http" ) func main() { r := gin.Default() // 創(chuàng)建一個(gè)默認(rèn)的Gin引擎 // 注冊(cè)中間件 r.Use(middleware.LoggerMiddleware()) // 定義路由 r.GET("/test", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "Hello, World!"}) }) err := r.Run(":8080") if err != nil { return } // 啟動(dòng)服務(wù)器 }
請(qǐng)求
可以看到日志中間件打印出的日志
2. 身份驗(yàn)證中間件
身份驗(yàn)證中間件用于驗(yàn)證用戶的身份,如檢查令牌、Cookie等。
package middleware import ( "fmt" "github.com/gin-gonic/gin" "net/http" ) // AuthMiddleware 是一個(gè)身份驗(yàn)證中間件 func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 假設(shè)我們使用Bearer令牌進(jìn)行身份驗(yàn)證 token := c.GetHeader("Authorization") //查看token fmt.Println("查看token:", token) if token != "Bearer your_token_here" { //認(rèn)證未通過(guò),返回401狀態(tài)碼和錯(cuò)誤消息 c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized access"}) c.Abort() return } // 調(diào)用后續(xù)的處理函數(shù) c.Next() } }
在main.go中使用該中間件:
package main import ( "github.com/gin-gonic/gin" "jingtian/jt_gin/middleware" "net/http" ) func main() { r := gin.Default() // 創(chuàng)建一個(gè)默認(rèn)的Gin引擎 // 注冊(cè)中間件 r.Use(middleware.AuthMiddleware()) // 定義路由 r.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "Hello, Authenticated User!"}) }) r.Run(":8080") // 啟動(dòng)服務(wù)器 }
3. 限流中間件
限流中間件用于限制特定時(shí)間段內(nèi)允許的請(qǐng)求數(shù)量,以防止服務(wù)器過(guò)載。
package main import ( "github.com/gin-gonic/gin" "time" ) var ( limiter = NewLimiter(10, 1*time.Minute) // 設(shè)置限流器,允許每分鐘最多請(qǐng)求10次 ) // NewLimiter 創(chuàng)建限流器 func NewLimiter(limit int, duration time.Duration) *Limiter { return &Limiter{ limit: limit, duration: duration, timestamps: make(map[string][]int64), } } // Limiter 限流器 type Limiter struct { limit int // 限制的請(qǐng)求數(shù)量 duration time.Duration // 時(shí)間窗口 timestamps map[string][]int64 // 請(qǐng)求的時(shí)間戳 } // Middleware 限流中間件 func (l *Limiter) Middleware(c *gin.Context) { ip := c.ClientIP() // 獲取客戶端IP地址 // 檢查請(qǐng)求時(shí)間戳切片是否存在 if _, ok := l.timestamps[ip]; !ok { l.timestamps[ip] = make([]int64, 0) } now := time.Now().Unix() // 當(dāng)前時(shí)間戳 // 移除過(guò)期的請(qǐng)求時(shí)間戳 for i := 0; i < len(l.timestamps[ip]); i++ { if l.timestamps[ip][i] < now-int64(l.duration.Seconds()) { l.timestamps[ip] = append(l.timestamps[ip][:i], l.timestamps[ip][i+1:]...) i-- } } // 檢查請(qǐng)求數(shù)量是否超過(guò)限制 if len(l.timestamps[ip]) >= l.limit { c.JSON(429, gin.H{ "message": "Too Many Requests", }) c.Abort() return } // 添加當(dāng)前請(qǐng)求時(shí)間戳到切片 l.timestamps[ip] = append(l.timestamps[ip], now) // 繼續(xù)處理請(qǐng)求 c.Next() } func main() { r := gin.Default() // 使用限流中間件 r.Use(limiter.Middleware) r.GET("/", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "Hello World", }) }) r.Run(":8080") }
每分鐘內(nèi)訪問(wèn)超過(guò)10次,就限流了
以上就是Golang Gin框架中間件的用法詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang Gin框架中間件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語(yǔ)言使用kafka-go實(shí)現(xiàn)Kafka消費(fèi)消息
本篇文章主要介紹了使用kafka-go庫(kù)消費(fèi)Kafka消息,包含F(xiàn)etchMessage和ReadMessage的區(qū)別和適用場(chǎng)景,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的可以了解一下2024-12-12基于Go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的計(jì)算器
這篇文章主要為大家詳細(xì)介紹了如何基于Go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的計(jì)算器,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以跟隨小編一起了解一下2023-10-10Go語(yǔ)言中GORM存取數(shù)組/自定義類型數(shù)據(jù)
在使用gorm時(shí)往往默認(rèn)的數(shù)據(jù)類型不滿足我們的要求,需要使用一些自定義數(shù)據(jù)類型作為字段類型,下面這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言中GORM存取數(shù)組/自定義類型數(shù)據(jù)的相關(guān)資料,需要的朋友可以參考下2023-01-01Golang分布式應(yīng)用定時(shí)任務(wù)示例詳解
這篇文章主要為大家介紹了Golang分布式應(yīng)用定時(shí)任務(wù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07解決Golang小數(shù)float64在實(shí)際工程中加減乘除的精度問(wèn)題
這篇文章主要介紹了解決Golang小數(shù)float64在實(shí)際工程中加減乘除的精度問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-03-03在 Golang 中實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Http中間件過(guò)程詳解
本文在go web中簡(jiǎn)單的實(shí)現(xiàn)了中間件的機(jī)制,這樣帶來(lái)的好處也是顯而易見的,當(dāng)然社區(qū)也有一些成熟的 middleware 組件,包括 Gin 一些Web框架中也包含了 middleware 相關(guān)的功能,具體內(nèi)容詳情跟隨小編一起看看吧2021-07-07Ubuntu下安裝Go語(yǔ)言開發(fā)環(huán)境及編輯器的相關(guān)配置
這篇文章主要介紹了Ubuntu下安裝Go語(yǔ)言開發(fā)環(huán)境及編輯器的相關(guān)配置,編輯器方面介紹了包括Vim和Eclipse,需要的朋友可以參考下2016-02-02Golang觀察者模式優(yōu)化訂單處理系統(tǒng)實(shí)例探究
當(dāng)涉及到訂單處理系統(tǒng)時(shí),觀察者設(shè)計(jì)模式可以用于實(shí)現(xiàn)訂單狀態(tài)的變化和通知,在這篇文章中,我們將介紹如何使用Golang來(lái)實(shí)現(xiàn)觀察者設(shè)計(jì)模式,并提供一個(gè)基于訂單處理系統(tǒng)的代碼示例2024-01-01Golang實(shí)現(xiàn)AES對(duì)稱加密算法實(shí)例詳解
所謂對(duì)稱加密是指在加密和解碼時(shí)使用同一密鑰的加密方式,下面這篇文章主要給大家介紹了關(guān)于Golang實(shí)現(xiàn)AES對(duì)稱加密算法的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02