1.Gin介绍

  • Gin 是一个高性能的Go语言Web框架,封装比较优雅,API友好,源代码比较明确。具有快速灵活,容错方便等特点。
  • 其实对于golang而言,web框架的依赖远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错。
  • 框架更像是一个常用函数或者工具的集合。借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。

Gin官网:Gin Github

2.Gin第一个应用

package main

import (
   "github.com/gin-gonic/gin"
   "net/http"
)

func main() {
   //实例化gin对象
   ginSer := gin.Default()
   //定义路由:当使用 GET 方法访问路径为 /api/gin-test 时,执行回调函数
   ginSer.GET("/api/gin-test", func(c *gin.Context) {
      //在回调函数中,返回一个字符串 "hello, go" 并设置HTTP状态码为200 OK
      c.String(http.StatusOK, "Hello Gin")
   })
   ginSer.Run(":8000")
}

2.1测试

//启动服务端
fei@feideMBP test % go run main.go 
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /api/gin-test             --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8000
[GIN] 2024/10/03 - 22:34:40 | 200 |      26.316µs |       127.0.0.1 | GET      "/api/gin-test"

//测试
fei@feideMBP ~ % curl 127.0.0.1:8000/api/gin-test 
Hello Gin

3.Gin工作流程

  • Engine容器对象,整个框架的基础
  • Engine.trees负责存储路由和handle方法的映射,采用类似字典树的结构
  • Engine.RouterGroup其中的Handlers存储着所有中间件
  • Context 上下文对象负责处理请求和回应,其中的handlers是存储处理请求时中间件和处理方法的

请求处理流程

4.Gin源码分析

4.1gin.Default()

// Default和New几乎是一模一样的,就是调用了Go泪痣的Logger,Recovery中间件
func Default() *Engine {
   debugPrintWARNINGDefault()
   engine := New()  // 默认实例
    // 注册中间件,中间件是一个函数,最终只要返回一个type HandlerFunc func (*Context) 就可以
   engine.Use(Logger(), Recovery())
   return engine
}

4.2engine:=New()初始化

通过调用gin.New()方法来实例化 Engine容器 .

  1. 初始化Engine
  2. RouterGroupHandlers(数组)设置成nil, basePath设置成/
  3. 为了使用方便, RouteGroup里面也有一个Engine指针, 这里将刚刚初始化的engine赋值给了RouterGroupengine指针
  4. 为了防止频繁的context GC造成效率的降低, 在Engine里使用了sync.Pool, 专门存储gin的Context
func New() *Engine {
	debugPrintWARNINGNew()
	// Engine 容器对象,整个框架的基础
	engine := &Engine{ // 初始化语句
		// Handlers 全局中间件组在注册路由时使用
		RouterGroup: RouterGroup{ // Engine.RouterGroup,其中的Handlers存储着所有中间件
			Handlers: nil,
			basePath: "/",
			root: true,
		},
		// 树结构,保存路由和处理方法的映射
		trees: make(methodTrees, 0, 9),
	}
	engine.RouterGroup.engine = engine
	return engine
}

4.3engine.Use()注册中间件

  • gin框架中的中间件设计很巧妙,可以首先从最常用的 r := gin.Default()的Default 函数开始看
  • 它内部构造一个新的engine之后就通过Use()函数注册了Logger中间件和Recovery中间件
  • Use()就是gin的引入中间件的入口了
  • 仔细分析这个函数, 不难发现Use()其实是在给RouteGroup引入中间件的.
func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())  // 默认注册的两个中间件
    return engine
}

gin.use()调用RouterGroup.Use()RouterGroup.Handlers写入记录

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
	engine.RouterGroup.Use(middleware...)
	engine.rebuild404Handlers() //注册404处理方法
	engine.rebuild405Handlers() //注册405处理方法
	return engine
}
// 其中`Handlers`字段就是一个数组,用来存储中间件
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}

组成一条处理函数链条HandlersChain

  • 也就是说,我们会将一个路由的中间件函数和处理函数结合到一起组成一条处理函数链条HandlersChain
  • 而它本质上就是一个由HandlerFunc组成的切片type HandlersChain []HandlerFunc

中间件的执行

其中c.Next()就是很关键的一步,它的代码很简单

从下面的代码可以看到,这里通过索引遍历HandlersChain链条,从而实现依次调用该路由的每一个函数(中间件或处理请求的函数),我们可以在中间件函数中通过再次调用c.Next()实现嵌套调用(func1中调用func2;func2中调用func3)

func (c *Context) Next() {
    c.index++
    for c.index < int8(len(c.handlers)) {
        c.handlers[c.index](c)
        c.index++
    }
}

4.4r.GET()注册路由

r.GET("/", func(c *gin.Context) {
    c.String(http.StatusOK, "hello World!")
})

通过Get方法将路由和处理视图函数注册

func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodGet, relativePath, handlers)
}

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
	absolutePath := group.calculateAbsolutePath(relativePath)
	handlers = group.combineHandlers(handlers)  // 将处理请求的函数与中间件函数结合
	group.engine.addRoute(httpMethod, absolutePath, handlers)   // 调用addRoute方法注册路由
	return group.returnObj()
}

  • 这段代码就是利用method, path, 将handlers注册到engine的trees中.
  • 注意这里为什么是HandlersChain呢, 可以简单说一下, 就是将中间件和处理函数都注册到method, path的tree中了.
// tree.go

// addRoute 将具有给定句柄的节点添加到路径中。
// 不是并发安全的
func (n *node) addRoute(path string, handlers HandlersChain) {
	fullPath := path
	n.priority++
	numParams := countParams(path)  // 数一下参数个数

	// 空树就直接插入当前节点
	if len(n.path) == 0 && len(n.children) == 0 {
		n.insertChild(numParams, path, fullPath, handlers)
		n.nType = root
		return
	}
	parentFullPathIndex := 0

walk:
	for {
		// 更新当前节点的最大参数个数
		if numParams > n.maxParams {
			n.maxParams = numParams
		}

		// 找到最长的通用前缀
		// 这也意味着公共前缀不包含“:”"或“*” /
		// 因为现有键不能包含这些字符。
		i := longestCommonPrefix(path, n.path)

		// 分裂边缘(此处分裂的是当前树节点)
		// 例如一开始path是search,新加入support,s是他们通用的最长前缀部分
		// 那么会将s拿出来作为parent节点,增加earch和upport作为child节点
		if i < len(n.path) {
			child := node{
				path:      n.path[i:],  // 公共前缀后的部分作为子节点
				wildChild:  n.wildChild,
				indices:    n.indices,
				children:   n.children,
				handlers:   n.handlers,
				priority:   n.priority - 1, //子节点优先级-1
				fullPath:   n.fullPath,
			}

			// Update maxParams (max of all children)
			for _, v := range child.children {
				if v.maxParams > child.maxParams {
					child.maxParams = v.maxParams
				}
			}

			n.children = []*node{&child}
			// []byte for proper unicode char conversion, see #65
			n.indices = string([]byte{n.path[i]})
			n.path = path[:i]
			n.handlers = nil
			n.wildChild = false
			n.fullPath = fullPath[:parentFullPathIndex+i]
		}

		// 将新来的节点插入新的parent节点作为子节点
		if i < len(path) {
			path = path[i:]

			if n.wildChild {  // 如果是参数节点
				parentFullPathIndex += len(n.path)
				n = n.children[0]
				n.priority++

				// Update maxParams of the child node
				if numParams > n.maxParams {
					n.maxParams = numParams
				}
				numParams--

				// 检查通配符是否匹配
				if len(path) >= len(n.path) && n.path == path[:len(n.path)] {
					// 检查更长的通配符, 例如 :name and :names
					if len(n.path) >= len(path) || path[len(n.path)] == '/' {
						continue walk
					}
				}

				pathSeg := path
				if n.nType != catchAll {
					pathSeg = strings.SplitN(path, "/", 2)[0]
				}
				prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
				panic("'" + pathSeg +
					"' in new path '" + fullPath +
					"' conflicts with existing wildcard '" + n.path +
					"' in existing prefix '" + prefix +
					"'")
			}
			// 取path首字母,用来与indices做比较
			c := path[0]

			// 处理参数后加斜线情况
			if n.nType == param && c == '/' && len(n.children) == 1 {
				parentFullPathIndex += len(n.path)
				n = n.children[0]
				n.priority++
				continue walk
			}

			// 检查路path下一个字节的子节点是否存在
			// 比如s的子节点现在是earch和upport,indices为eu
			// 如果新加一个路由为super,那么就是和upport有匹配的部分u,将继续分列现在的upport节点
			for i, max := 0, len(n.indices); i < max; i++ {
				if c == n.indices[i] {
					parentFullPathIndex += len(n.path)
					i = n.incrementChildPrio(i)
					n = n.children[i]
					continue walk
				}
			}

			// 否则就插入
			if c != ':' && c != '*' {
				// []byte for proper unicode char conversion, see #65
				// 注意这里是直接拼接第一个字符到n.indices
				n.indices += string([]byte{c})
				child := &node{
					maxParams: numParams,
					fullPath:  fullPath,
				}
				// 追加子节点
				n.children = append(n.children, child)
				n.incrementChildPrio(len(n.indices) - 1)
				n = child
			}
			n.insertChild(numParams, path, fullPath, handlers)
			return
		}

		// 已经注册过的节点
		if n.handlers != nil {
			panic("handlers are already registered for path '" + fullPath + "'")
		}
		n.handlers = handlers
		return
	}
}

4.5r.run()启动服务

  • 通过调用net/http来启动服务,由于engine实现了ServeHTTP方法
  • 只需要直接传engine对象就可以完成初始化并启动
//r.Run()
func (engine *Engine) Run(addr ...string) (err error) {
	defer func() { debugPrintError(err) }()
	address := resolveAddress(addr)
	debugPrint("Listening and serving HTTP on %s\n", address)
	err = http.ListenAndServe(address, engine)    // gin使用net/http模块
	return
}
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}
//来自 net/http 定义的接口,只要实现了这个接口就可以作为处理请求的函数
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}
//实现了ServeHTTP方法
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	c := engine.pool.Get().(*Context)
	c.writermem.reset(w)
	c.Request = req
	c.reset()
	engine.handleHTTPRequest(c)
	engine.pool.Put(c)
}

5.Gin路由与传参

5.1无参路由

package main

import (
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
)

func HelloGinHandler(c *gin.Context) {
	// gin.Context,封装了request和response
	c.String(http.StatusOK, "Hello Gin")
}

func main() {
	//实例化gin对象
	ginSer := gin.Default()
	// 基本路由 /hello/
	ginSer.GET("/hello", HelloGinHandler)
	log.Printf("PATH: %s", "http://127.0.0.1:8000")
	ginSer.Run(":8000")
}

5.2api参数

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
)

// uri路径传参
func GetBookDetailHandler(c *gin.Context) {
	bookId := c.Param("id")
	c.String(http.StatusOK, fmt.Sprintf("成功获取书籍详情:%s", bookId))
}

// ?号传参
func GetUserDetailHandler(c *gin.Context) {
	username := c.Query("name")
	// gin.Context,封装了request和response
	c.String(http.StatusOK, fmt.Sprintf("成功获取用户详情:%s", username))
}

// 组合使用
func GetFoodDetailHandler(c *gin.Context) {
	Food := c.Param("name")
	Weight := c.Query("weight")
	c.String(http.StatusOK, fmt.Sprintf("成功获取食物:%s,重%v斤", Food, Weight))
}

func main() {
	//实例化gin对象
	ginSer := gin.Default()
	//注册路由
	ginSer.GET("/book/:id", GetBookDetailHandler)
	ginSer.GET("/user", GetUserDetailHandler)
	ginSer.GET("/food/:name", GetFoodDetailHandler)
	log.Printf("PATH: %s", "http://127.0.0.1:8000")
	ginSer.Run(":8000")
}

测试
fei@feideMBP ~ % curl 127.0.0.1:8000/book/9623503
成功获取书籍详情:9623503                       
fei@feideMBP ~ % curl 127.0.0.1:8000/user?name=虞锋
成功获取用户详情:虞锋
fei@feideMBP ~ % curl 127.0.0.1:8000/food/cake?weight=256
成功获取食物:cake,重256斤

5.3ShouldBind参数绑定

Gin提供了一个更加便捷、开发效率更高的方法获取参数,也就是参数绑定。

  • 我们可以基于请求的Content-Type识别请求数据类型并利用反射机制
  • 自动提取请求中QueryString、form表单、JSON、XML等参数到结构体中。
  • 下面的示例代码将演示.ShouldBind()强大的功能,它能够自动提取基于querystring、form、json、path的参数,并把值绑定到指定的结构体对象。
package main

import (
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
)

/*
binding 用于必填参数校验、
json 用于将application/json请求绑定到结构体、
form 用于将multipart/form-data请求绑定到结构体
*/
type Login struct {
	Username string `form:"username" json:"username" binding:"required"`
	Password string `form:"password" json:"password" binding:"required"`
}

func LoginHandler(c *gin.Context) {
	login := &Login{}
	//GET请求一般使用form标签
	//application/json一般是POST、PUT、DELETE

	if err := c.ShouldBind(&login); err != nil {
		//gin.H 为一个map
		c.JSON(http.StatusOK, gin.H{
			"msg":  "绑定参数失败" + err.Error(),
			"data": nil,
			"code": 90400,
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"msg":  "绑定参数成功",
		"data": login,
		"code": 90200,
	})
}

func main() {
	//实例化gin对象
	ginSer := gin.Default()
	//注册路由
	ginSer.POST("/login", LoginHandler)
	ginSer.GET("/login", LoginHandler)
	log.Printf("PATH: %s", "http://127.0.0.1:8000")
	ginSer.Run(":8000")
}

测试

POST json测试
fei@feideMBP ~ % curl --location 'http://127.0.0.1:8000/login' \
--header 'Content-Type: application/json' \
--data '{
    "username": "fei",
    "password": "123456"
}'
{"code":90200,"data":{"username":"fei","password":"123456"},"msg":"绑定参数成功"}

fei@feideMBP ~ % curl --location 'http://127.0.0.1:8000/login' \
--header 'Content-Type: application/json' \
--data '{
    "username": "fei"
}'
{"code":90400,"data":null,"msg":"绑定参数失败Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}

GET form测试
fei@feideMBP ~ % curl --location --request GET 'http://127.0.0.1:8000/login' \
--form 'username="fly"' \
--form 'password="123456"'
{"code":90200,"data":{"username":"fly","password":"123456"},"msg":"绑定参数成功"}

fei@feideMBP ~ % curl --location --request GET 'http://127.0.0.1:8000/login' \
--form 'username="fly"'
{"code":90400,"data":null,"msg":"绑定参数失败Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}

5.4重定向

package main

import (
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
)

func main() {
	//实例化gin对象
	ginSer := gin.Default()
	//重定向
	ginSer.GET("/remove", func(context *gin.Context) {
		context.Redirect(http.StatusMovedPermanently, "https://cakepanit.com")
	})
	log.Printf("PATH: %s", "http://127.0.0.1:8000")
	ginSer.Run(":8000")
}

测试

fei@feideMBP ~ % curl -i http://127.0.0.1:8000/remove

HTTP/1.1 301 Moved Permanently
Content-Type: text/html; charset=utf-8
Location: https://cakepanit.com
Date: Fri, 04 Oct 2024 16:17:13 GMT
Content-Length: 56

<a href="https://cakepanit.com">Moved Permanently</a>.

5.5路由分发

目录结构

fei@feideMBP test % tree ./
./
├── go.mod
├── go.sum
├── main.go
└── routers
    ├── book.go
    └── user.go

router1

package routers

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func UserHandler(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"msg":  "User Router",
		"data": nil,
		"code": "90200",
	})
}

// 在main中初始化gin对象之后,调用此方法传入gin对象,就注册了这个路由
func LoadUsers(r *gin.Engine) {
	r.GET("/users", UserHandler)
}
router2
package routers

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func BookHandler(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"msg":  "Book Router",
		"data": nil,
		"code": "90200",
	})
}

// 在main中初始化gin对象之后,调用此方法传入gin对象,就注册了这个路由
func LoadBooks(r *gin.Engine) {
	r.GET("/books", BookHandler)
}

main

package main

import (
	"github.com/gin-gonic/gin"
	"log"
	"test/routers"
)

func main() {
	//实例化gin对象
	ginSer := gin.Default()
	//注册路由
	routers.LoadUsers(ginSer)
	routers.LoadBooks(ginSer)

	log.Printf("PATH: %s", "http://127.0.0.1:8000")
	ginSer.Run(":8000")
}

测试

fei@feideMBP ~ % curl http://127.0.0.1:8000/users
{"code":"90200","data":null,"msg":"User Router"}

fei@feideMBP ~ % curl http://127.0.0.1:8000/books
{"code":"90200","data":null,"msg":"Book Router"}

5.6routes group

  • routes group是为了管理一些相同的URL
package main

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

// gin的helloWorld
func main() {
   // 1.创建路由
   // 默认使用了2个中间件Logger(), Recovery()
   r := gin.Default()
   // 路由组1 ,处理GET请求
   v1 := r.Group("/v1")
   // {} 是书写规范
   {
      v1.GET("/login", login)
      v1.GET("submit", submit)
   }
   v2 := r.Group("/v2")
   {
      v2.POST("/login", login)
      v2.POST("/submit", submit)
   }
   r.Run(":8000")
}

func login(c *gin.Context) {
   name := c.DefaultQuery("name", "jack")
   c.String(200, fmt.Sprintf("hello %s\n", name))
}

func submit(c *gin.Context) {
   name := c.DefaultQuery("name", "lily")
   c.String(200, fmt.Sprintf("hello %s\n", name))
}

6.Gin中间件

  • Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。
  • 这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑
  • 比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

6.1全局中间件

  • 所有请求都经过此中间件
  • 中间件中没有调用next方法,先走完中间件才会执行视图函数
package main

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

// 定义中间件
func MiddleWare() gin.HandlerFunc {
	return func(c *gin.Context) {
		nowTime := time.Now()
		log.Printf("中间件开始执行,%v", nowTime)
		//设置变了到Context的key中,可以通过Get()取
		c.Set("middle", "全局中间件")
		status := c.Writer.Status() //获取状态码
		sinceTime := time.Since(nowTime)
		log.Printf("中间件执行完毕,%v,%v", sinceTime, status)
	}
}

func main() {
	//实例化gin对象
	ginSer := gin.Default()
	//注册全局中间件
	ginSer.Use(MiddleWare())
	{
		ginSer.GET("/Global", func(c *gin.Context) {
			//取中间件set的值
			middleValue, _ := c.Get("middle")
			c.JSON(200, gin.H{
				"msg":  "执行成功",
				"data": middleValue,
				"code": "90200",
			})
		})
	}

	log.Printf("PATH: %s", "http://127.0.0.1:8000")
	ginSer.Run(":8000")
}

测试

fei@feideMBP test % go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /Global                   --> main.main.func1 (4 handlers)
2024/10/05 01:23:11 PATH: http://127.0.0.1:8000
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8000
2024/10/05 01:23:30 中间件开始执行,2024-10-05 01:23:30.308065 +0800 CST m=+18.718944754
2024/10/05 01:23:30 中间件执行完毕,29.735µs,200
[GIN] 2024/10/05 - 01:23:30 | 200 |      71.342µs |       127.0.0.1 | GET      "/Global"

fei@feideMBP ~ % curl http://127.0.0.1:8000/Global
{"code":"90200","data":"全局中间件","msg":"执行成功"}

6.2局部中间件

  • 局部中间件只会在局部执行
package main

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

// 定义中间件
func MiddleWare() gin.HandlerFunc {
	return func(c *gin.Context) {
		nowTime := time.Now()
		log.Printf("中间件开始执行,%v", nowTime)
		//设置变了到Context的key中,可以通过Get()取
		c.Set("middle", "局部中间件")
		status := c.Writer.Status() //获取状态码
		sinceTime := time.Since(nowTime)
		log.Printf("中间件执行完毕,%v,%v", sinceTime, status)
	}
}

func main() {
	//实例化gin对象
	ginSer := gin.Default()
	//注册局部中间件,只对当前方法生效
	ginSer.GET("/Global", MiddleWare(), func(c *gin.Context) {
		//取中间件set的值
		middleValue, _ := c.Get("middle")
		c.JSON(200, gin.H{
			"msg":  "执行成功",
			"data": middleValue,
			"code": "90200",
		})
	})

	log.Printf("PATH: %s", "http://127.0.0.1:8000")
	ginSer.Run(":8000")
}

测试

fei@feideMBP test % go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /Global                   --> main.main.func1 (4 handlers)
2024/10/05 01:27:47 PATH: http://127.0.0.1:8000
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8000
2024/10/05 01:27:50 中间件开始执行,2024-10-05 01:27:50.421581 +0800 CST m=+3.358432915
2024/10/05 01:27:50 中间件执行完毕,50.829µs,200
[GIN] 2024/10/05 - 01:27:50 | 200 |      90.149µs |       127.0.0.1 | GET      "/Global"

fei@feideMBP ~ % curl http://127.0.0.1:8000/Global
{"code":"90200","data":"局部中间件","msg":"执行成功"}

6.3Next()方法

  • 在中间件中调用next()方法,会从next()方法调用的地方跳转到下一个视图函数(可能是中间件也可能是业务方法)
    • 中间件代码最后即使没有调用Next()方法,后续中间件及handlers也会执行;
  • 如果在中间件函数的非结尾调用Next()方法当前中间件剩余代码会被暂停执行,会先去执行后续中间件及handlers,等这些handlers全部执行完以后程序控制权会回到当前中间件继续执行剩余代码;

package main

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

// 定义中间件
func MiddleWare1() gin.HandlerFunc {
	return func(c *gin.Context) {
		nowTime := time.Now()
		log.Printf("中间件1开始执行,%v", nowTime)
		c.Next()
		sinceTime := time.Since(nowTime)
		log.Printf("中间件1执行完毕,%v", sinceTime)
	}
}

func MiddleWare2() gin.HandlerFunc {
	return func(c *gin.Context) {
		nowTime := time.Now()
		log.Printf("中间件2开始执行,%v", nowTime)
		c.Next()
		sinceTime := time.Since(nowTime)
		log.Printf("中间件2执行完毕,%v", sinceTime)
	}
}

func main() {
	//实例化gin对象
	ginSer := gin.Default()
	//注册局部中间件,只对当前方法生效
	ginSer.GET("/Global", MiddleWare1(), MiddleWare2(), func(c *gin.Context) {
		c.JSON(200, gin.H{
			"msg":  "执行成功",
			"data": "Next测试",
			"code": "90200",
		})
		log.Printf("业务视图执行完成")
	})

	log.Printf("PATH: %s", "http://127.0.0.1:8000")
	ginSer.Run(":8000")
}
测试
fei@feideMBP test % go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /Global                   --> main.main.func1 (5 handlers)
2024/10/05 02:16:33 PATH: http://127.0.0.1:8000
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8000
2024/10/05 02:16:37 中间件1开始执行,2024-10-05 02:16:37.29812 +0800 CST m=+3.993203377
2024/10/05 02:16:37 中间件2开始执行,2024-10-05 02:16:37.298147 +0800 CST m=+3.993230463
2024/10/05 02:16:37 业务视图执行完成
2024/10/05 02:16:37 中间件2执行完毕,71.999µs
2024/10/05 02:16:37 中间件1执行完毕,102.562µs
[GIN] 2024/10/05 - 02:16:37 | 200 |     106.552µs |       127.0.0.1 | GET      "/Global"

fei@feideMBP ~ % curl http://127.0.0.1:8000/Global
{"code":"90200","data":"Next测试","msg":"执行成功"}

6.5Abort()方法

  • 如果想中断剩余中间件及handlers应该使用Abort方法,但需要注意当前中间件的剩余代码会继续执行。
package main

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

// 定义中间件
func MiddleWare1() gin.HandlerFunc {
	return func(c *gin.Context) {
		nowTime := time.Now()
		log.Printf("中间件1开始执行,%v", nowTime)
		//中断后续Handler执行
		c.Abort()
		sinceTime := time.Since(nowTime)
		log.Printf("中间件1执行完毕,%v", sinceTime)
	}
}

func MiddleWare2() gin.HandlerFunc {
	return func(c *gin.Context) {
		nowTime := time.Now()
		log.Printf("中间件2开始执行,%v", nowTime)
		c.Next()
		sinceTime := time.Since(nowTime)
		log.Printf("中间件2执行完毕,%v", sinceTime)
	}
}

func main() {
	//实例化gin对象
	ginSer := gin.Default()
	//注册局部中间件,只对当前方法生效
	ginSer.GET("/Global", MiddleWare1(), MiddleWare2(), func(c *gin.Context) {
		c.JSON(200, gin.H{
			"msg":  "执行成功",
			"data": "Next测试",
			"code": "90200",
		})
		log.Printf("业务视图执行完成")
	})

	log.Printf("PATH: %s", "http://127.0.0.1:8000")
	ginSer.Run(":8000")
}

测试

fei@feideMBP test % go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /Global                   --> main.main.func1 (5 handlers)
2024/10/05 02:27:05 PATH: http://127.0.0.1:8000
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8000
2024/10/05 02:27:14 中间件1开始执行,2024-10-05 02:27:14.554943 +0800 CST m=+9.109470469
2024/10/05 02:27:14 中间件1执行完毕,54.262µs
[GIN] 2024/10/05 - 02:27:14 | 200 |       58.93µs |       127.0.0.1 | GET      "/Global"


fei@feideMBP ~ % curl http://127.0.0.1:8000/Global
fei@feideMBP ~ % 

7.Gin-AuthDemo

  • 用Gin实现一个简单的鉴权
package main

import (
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
)

// 定义中间件
func AuthMiddleWare() gin.HandlerFunc {
	return func(c *gin.Context) {
		if Token := c.Request.Header.Get("token"); Token == "" {
			c.JSON(http.StatusForbidden, gin.H{
				"msg": "鉴权失败",
			})
			log.Printf("鉴权失败")
			c.Abort()
		}
	}
}

func main() {
	//实例化gin对象
	ginSer := gin.Default()
	//注册局部中间件,只对当前方法生效
	ginSer.GET("/index", AuthMiddleWare(), func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg":  "鉴权成功",
			"data": "Welcome to my homepage",
			"code": "90200",
		})
		log.Printf("业务视图执行完成")
	})

	log.Printf("PATH: %s", "http://127.0.0.1:8000")
	ginSer.Run(":8000")
}

测试

fei@feideMBP test % go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /index                    --> main.main.func1 (4 handlers)
2024/10/05 02:47:52 PATH: http://127.0.0.1:8000
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8000
2024/10/05 02:48:01 鉴权失败
[GIN] 2024/10/05 - 02:48:01 | 403 |     224.271µs |       127.0.0.1 | GET      "/index"
2024/10/05 02:48:10 业务视图执行完成
[GIN] 2024/10/05 - 02:48:10 | 200 |      66.163µs |       127.0.0.1 | GET      "/index"

fei@feideMBP ~ % curl --location 'http://127.0.0.1:8000/index'
{"msg":"鉴权失败"}

fei@feideMBP ~ % curl --location 'http://127.0.0.1:8000/index' \
--header 'token: 123456'
{"code":"90200","data":"Welcome to my homepage","msg":"鉴权成功"}

Last updated 05 Oct 2024, 02:58 +0800 . history