Go Gin 快速学习 【Gin 中间件】

Gin 中间件

定义中间件

  • 中间件必须是一个gin.HandlerFunc类型。
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"time"
)

// 计时中间件
func Timer1() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		c.Next() // 调用后续中间件和路由处理
		//c.Abort() // 中断后续中间件和路由处理,返回给客户端。相当于return了
		end := time.Now()
		fmt.Println("耗时1:", end.Sub(start))
	}
}

// 计时中间件
func Timer2() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		c.Next()
		end := time.Now()
		fmt.Println("耗时2:", end.Sub(start))
	}
}

func main() {
	r := gin.Default()

	r.Use(Timer1(), Timer2()) // 注册中间件

	r.GET("/", func(c *gin.Context) {
		time.Sleep(3 * time.Second)
		c.JSON(200, gin.H{
			"code": 200,
			"msg":  "ok",
		})
	})
	r.Run(":8080")
}

注册中间件

  • 全局注册 中间件

    package main
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    	"time"
    )
    
    // 计时中间件
    func Timer1() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		println("注册顺序1")
    		start := time.Now()
    		c.Next() // 调用后续中间件和路由处理
    		//c.Abort() // 中断后续中间件和路由处理,返回给客户端。相当于return了
    		end := time.Now()
    		fmt.Println("响应顺序1:", end.Sub(start))
    	}
    }
    
    
    func main() {
    	r := gin.Default()
    
    	r.Use(Timer1()) // 注册全局中间件
    
    	r.GET("/", func(c *gin.Context) {
    		time.Sleep(3 * time.Second)
    		c.JSON(200, gin.H{
    			"code": 200,
    			"msg":  "ok",
    		})
    	})
    
    	r.Run(":8080")
    }
    // 中间件注册结果
    /*
    
    注册顺序1
    
    响应顺序1
    
    */
    
  • 为路由组 添加中间件

    package main
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    	"time"
    )
    
    func Timer() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		start := time.Now()
    		c.Next()
    		end := time.Now()
    		fmt.Println("耗时:", end.Sub(start))
    	}
    }
    
    func main() {
    	// 创建一个默认的路由引擎
    	r := gin.Default()
    
    	// 创建一个路由组,路径为 "/test"
    	testRouter := r.Group("/test")
    	// 路由组添加中间件
    	testRouter.Use(Timer())
    	// 路由组添加路由
    	testRouter.GET("/", func(c *gin.Context) {
    		time.Sleep(3 * time.Second)
    		c.JSON(200, gin.H{
    			"code": 200,
    			"msg":  "ok",
    		})
    	})
    
    	// 启动 HTTP 服务器,默认监听在 0.0.0.0:8080
    	r.Run() // 等价于 r.Run(":8080")
    }
    
    
  • 为单个路由 添加中间件

    package main
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    	"time"
    )
    
    func Timer02() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		start := time.Now()
    		c.Next()
    		end := time.Now()
    		fmt.Println("耗时:", end.Sub(start))
    	}
    }
    
    func main() {
    	// 创建一个默认的路由引擎
    	r := gin.Default()
    	// 给一个【普通路由】添加中间件
    	r.GET("/", Timer02(), func(c *gin.Context) {
    		time.Sleep(3 * time.Second)
    		c.JSON(200, gin.H{
    			"code": 200,
    			"msg":  "ok",
    		})
    	})
    
    	// 启动 HTTP 服务器,默认监听在 0.0.0.0:8080
    	r.Run() // 等价于 r.Run(":8080")
    }
    
    
  • 注册多个中间件执行顺序

    package main
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    	"time"
    )
    
    // 计时中间件
    func Timer1() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		println("注册顺序1")
    		start := time.Now()
    		c.Next() // 调用后续中间件和路由处理
    		//c.Abort() // 中断后续中间件和路由处理,返回给客户端。相当于return了
    		end := time.Now()
    		fmt.Println("响应顺序1:", end.Sub(start))
    	}
    }
    
    // 计时中间件
    func Timer2() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		println("注册顺序2")
    		start := time.Now()
    		c.Next()
    		end := time.Now()
    		fmt.Println("响应顺序2:", end.Sub(start))
    	}
    }
    
    // 计时中间件
    func Timer3() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		println("注册顺序3")
    		start := time.Now()
    		c.Next()
    		end := time.Now()
    		fmt.Println("响应顺序3:", end.Sub(start))
    	}
    }
    
    func main() {
    	r := gin.Default()
    
    	r.Use(Timer1(), Timer2()) // 注册中间件
    	r.Use(Timer3())
    	r.GET("/", func(c *gin.Context) {
    		time.Sleep(3 * time.Second)
    		c.JSON(200, gin.H{
    			"code": 200,
    			"msg":  "ok",
    		})
    	})
    
    	r.Run(":8080")
    }
    // 中间件注册结果
    /*
    
    注册顺序1
    注册顺序2
    注册顺序3
    响应顺序3: 83ns
    响应顺序2: 27.334µs
    响应顺序1: 38.542µs
    
    
    */
    

中间件使用事项

  • Gin的默认中间件

    package main
    
    import "github.com/gin-gonic/gin"
    
    func main() {
    	// 创建一个默认的路由引擎
    	r := gin.Default()
    
    	// 默认中间件:Logger:日志中间件, Recovery:恢复中间件
    	r.Use(gin.Logger(), gin.Recovery())
    
    	r.GET("/ping", func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message": "pong",
    		})
    	})
    	r.Run(":8080")
    }
    
    
  • 中间件中使用 goroutine的问题

    1. Gin上下文的生命周期问题

    Gin的上下文(*gin.Context)是请求级别的,这意味着:

    • 每个HTTP请求都会创建一个新的上下文实例
    • 当请求处理完成后,上下文会被回收和重用(通过sync.Pool)
    • 如果在goroutine中直接使用原始上下文,当主请求处理完成时,上下文可能已经被回收
    1. 竞态条件(Race Condition)

    当你在goroutine中直接使用原始上下文时,可能会发生:

    • 主goroutine完成请求处理,释放上下文
    • 异步goroutine还在访问上下文的数据
    • 导致数据竞争或访问已释放的内存
    1. c.Copy() 的作用

    c.Copy() 方法创建上下文的只读副本,具有以下特点:

    // c.Copy() 返回的副本包含:
    - 请求信息(Request)的副本
    - 响应信息(Writer)的副本(但写入操作会被忽略)
    - 参数(Params)的副本
    - 处理器链(Handlers)的副本
    - 但**不包含**连接池等共享资源
    
    package main
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    	"time"
    )
    
    // 深度解读:Gin中间件中使用goroutine的问题
    
    // WrongGoroutineHandler 1. 错误的示例:在goroutine中直接使用原始上下文
    func WrongGoroutineHandler() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		fmt.Println("=== 错误示例开始 ===")
    		fmt.Printf("原始上下文地址: %p\n", c)
    
    		// 错误:在goroutine中直接使用原始上下文
    		go func() {
    			// 模拟异步处理(比如日志记录、数据分析等)
    			time.Sleep(200 * time.Millisecond)
    
    			// 问题1:上下文可能已经被回收
    			fmt.Printf("在goroutine中访问上下文地址: %p\n", c)
    			fmt.Printf("在goroutine中访问URL: %s\n", c.Request.URL.Path)
    
    			// 问题2:可能引发竞态条件
    			// 主goroutine可能已经完成了请求处理,释放了上下文
    			// 这里访问c的任何数据都可能导致panic或数据不一致
    		}()
    
    		c.Next()
    		fmt.Println("=== 错误示例结束 ===")
    	}
    }
    
    // CorrectGoroutineHandler 2. 正确的示例:使用上下文的只读副本
    func CorrectGoroutineHandler() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		fmt.Println("=== 正确示例开始 ===")
    		fmt.Printf("原始上下文地址: %p\n", c)
    
    		// 正确:创建上下文的只读副本
    		copyCtx := c.Copy()
    		fmt.Printf("副本上下文地址: %p\n", copyCtx)
    
    		go func(ctx *gin.Context) {
    			// 模拟异步处理
    			time.Sleep(200 * time.Millisecond)
    
    			// 安全:使用副本上下文
    			fmt.Printf("在goroutine中访问副本上下文地址: %p\n", ctx)
    			fmt.Printf("在goroutine中访问副本URL: %s\n", ctx.Request.URL.Path)
    
    			// 副本是只读的,不会影响原始请求
    			// 可以安全地读取请求信息,但不能修改响应
    		}(copyCtx)
    
    		c.Next()
    		fmt.Println("=== 正确示例结束 ===")
    	}
    }
    
    // RaceConditionDemoHandler 3. 演示竞态条件的示例
    func RaceConditionDemoHandler() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		fmt.Println("=== 竞态条件演示开始 ===")
    
    		// 模拟多个goroutine同时访问上下文
    		for i := 0; i < 3; i++ {
    			go func(index int) {
    				time.Sleep(time.Duration(index) * 100 * time.Millisecond)
    
    				// 这里可能发生竞态条件
    				// 不同的goroutine可能同时访问c的相同数据,导致竞态条件
    				fmt.Printf("Goroutine %d 访问URL: %s\n", index, c.Request.URL.Path)
    			}(i)
    		}
    
    		c.Next()
    		fmt.Println("=== 竞态条件演示结束 ===")
    	}
    }
    
    // SafeConcurrentHandler 4. 使用副本避免竞态条件的示例
    func SafeConcurrentHandler() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		fmt.Println("=== 安全并发示例开始 ===")
    
    		// 为每个goroutine创建独立的副本
    		for i := 0; i < 3; i++ {
    			copyCtx := c.Copy()
    
    			go func(index int, ctx *gin.Context) {
    				time.Sleep(time.Duration(index) * 100 * time.Millisecond)
    
    				// 每个goroutine使用自己的副本,避免竞态条件
    				fmt.Printf("Goroutine %d 访问副本URL: %s\n", index, ctx.Request.URL.Path)
    			}(i, copyCtx)
    		}
    
    		c.Next()
    		fmt.Println("=== 安全并发示例结束 ===")
    	}
    }
    
    // AsyncLoggingHandler 5. 实际应用场景:异步日志记录
    func AsyncLoggingHandler() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		start := time.Now()
    
    		// 创建副本用于异步日志记录
    		logCtx := c.Copy()
    
    		// 异步记录请求日志(不影响主请求的响应时间)
    		go func(ctx *gin.Context, startTime time.Time) {
    			// 模拟日志写入(可能是文件、数据库等)
    			time.Sleep(50 * time.Millisecond)
    
    			duration := time.Since(startTime)
    			fmt.Printf("[ASYNC LOG] %s %s - %v\n",
    				ctx.Request.Method,
    				ctx.Request.URL.Path,
    				duration)
    		}(logCtx, start)
    
    		c.Next()
    
    		// 主请求继续处理,不等待日志记录完成
    	}
    }
    
    // AsyncDataProcessingHandler 6. 实际应用场景:异步数据处理
    func AsyncDataProcessingHandler() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		// 创建副本用于异步数据处理
    		dataCtx := c.Copy()
    
    		// 异步处理数据(比如发送到消息队列、更新统计信息等)
    		go func(ctx *gin.Context) {
    			// 模拟数据处理
    			time.Sleep(100 * time.Millisecond)
    
    			// 安全地读取请求数据
    			userAgent := ctx.Request.UserAgent()
    			ip := ctx.ClientIP()
    
    			fmt.Printf("[DATA PROCESSING] UserAgent: %s, IP: %s\n", userAgent, ip)
    		}(dataCtx)
    
    		c.Next()
    	}
    }
    
    func main() {
    	// 创建一个默认的路由引擎
    	r := gin.Default()
    
    	// 注册各种演示中间件
    	r.Use(AsyncLoggingHandler())        // 异步日志记录
    	r.Use(AsyncDataProcessingHandler()) // 异步数据处理
    
    	// 测试路由
    	r.GET("/ping", func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message":   "pong",
    			"timestamp": time.Now().Unix(),
    		})
    	})
    
    	// 演示正确用法的路由
    	r.GET("/correct", CorrectGoroutineHandler(), func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message": "正确使用goroutine的示例",
    		})
    	})
    
    	// 演示安全并发的路由
    	r.GET("/safe", SafeConcurrentHandler(), func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message": "安全并发处理示例",
    		})
    	})
    
    	// 警告:演示错误用法的路由(实际生产环境应避免)
    	r.GET("/wrong", WrongGoroutineHandler(), func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message": "错误使用goroutine的示例(可能引发问题)",
    		})
    	})
    
    	// 警告:演示竞态条件的路由(实际生产环境应避免)
    	r.GET("/race", RaceConditionDemoHandler(), func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message": "竞态条件演示示例(可能引发问题)",
    		})
    	})
    
    	fmt.Println("服务器启动在 :8080")
    	fmt.Println("访问以下路由进行测试:")
    	fmt.Println("  GET /ping      - 基础测试")
    	fmt.Println("  GET /correct   - 正确用法演示")
    	fmt.Println("  GET /safe      - 安全并发演示")
    	fmt.Println("  GET /wrong     - 错误用法演示(谨慎使用)")
    	fmt.Println("  GET /race      - 竞态条件演示(谨慎使用)")
    
    	r.Run(":8080")
    }
    
    
  • Gin 处理 Cors 跨域

    package main
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    )
    
    func Cors() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		fmt.Printf("请求路径:%s\n", c.Request.URL.Path)
    		fmt.Printf("请求方法:%s\n", c.Request.Method)
    		// 直接设置允许所有来源,移除域名白名单检查
    		c.Header("Access-Control-Allow-Origin", "*")
    		c.Header("Access-Control-Allow-Credentials", "true")
    		c.Header("Access-Control-Allow-Headers",
    			"Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, Accept, Origin, Cache-Control, X-Requested-With")
    		c.Header("Access-Control-Allow-Methods",
    			"POST, OPTIONS, GET, PUT, DELETE, PATCH")
    		c.Header("Access-Control-Max-Age", "86400") // 1天缓存
    
    		// 处理预检请求
    		if c.Request.Method == "OPTIONS" {
    			c.AbortWithStatus(204)
    			return
    		}
    
    		c.Next()
    	}
    }
    
    func main() {
    	r := gin.Default()
    	// 给所有路由添加中间件
    	r.Use(Cors())
    	// 定义一个 GET 请求的路由,路径为 "/"
    	r.GET("/", func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message": "hello world",
    		})
    	})
    	// 启动 HTTP 服务器,默认监听在 0.0.0.0:8080
    	r.Run(":8080")
    }
    
    

代码地址:

https://github.com/dengshuaix/go_gin_study

学习博客:

https://www.liwenzhou.com/posts/Go/gin/#c-9-3

posted @ 2025-11-17 18:09  染指未来  阅读(34)  评论(0)    收藏  举报