首页 > 编程语言 >gin源码学习-中间件解析(3)

gin源码学习-中间件解析(3)

时间:2022-12-09 16:55:39浏览次数:48  
标签:engine group handlers 中间件 源码 gin 路由

目录

1.gin中间件

中间件直接说源码优点枯燥,不妨从demo来以小窥大。

package main

import (
	"log"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
)

func main() {
	engine := gin.New()

	server := http.Server{
		Addr: ":8080",
		// called handler, if nil default http.DefaultServeMux
		Handler:      engine,
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
	}
	// 注册全局中间件
	// recovery中间件, 记录日志中间件, 请求处理时间耗时中间件
	engine.Use(gin.Recovery(), webLogMiddleware(), timeRecordMiddleware())
	// 注册组中间件
	// 除注册用户外,其余组内路由访问需要认证的中间件
	// 方法1,组内加中间件
	group1 := engine.Group("/app/v1", authRequiredMiddleware()) 
	// 方法2,组内Use()加中间件
	// 通过组路由Use()方法添加中间件
	// group1.Use(authRequiredMiddleware()) 
	{
		group1.POST("/add", addUser)
		group1.GET("/get", getUser)
		group1.DELETE("/delete", deleteUser)
	}
	// 单个组路添加中间件
	engine.GET("/ping", myMiddleware(), pong)

	err := server.ListenAndServe()
	if err != nil {
		log.Fatalln("server start failed:", err)
	}
}

func pong(ctx *gin.Context) {
	ctx.JSON(http.StatusOK, gin.H{
		"msg": "pong",
	})
}

func addUser(ctx *gin.Context)  {
	// 具体业务逻辑...
	ctx.JSON(http.StatusOK, gin.H{
		"msg": "add user success.",
	})
}

func getUser(ctx *gin.Context)  {
	// 具体业务逻辑...
	ctx.JSON(http.StatusOK, gin.H{
		"msg": "get user info success.",
		"username": "Alice",
		"address": "Tianfu 1st",
	})
}

func deleteUser(ctx *gin.Context)  {
	// 具体业务逻辑...
	ctx.JSON(http.StatusOK, gin.H{
		"msg": "delete user success.",
	})
}

// 时间耗时中间件
func timeRecordMiddleware() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		start := time.Now()

		ctx.Next()

		// 模拟请求处理耗时
		time.Sleep(2 * time.Second)

		cost := time.Since(start).Seconds()
		log.Printf("request handle time cost: %f\n", cost)
	}
}

// 记录日志中间件
func webLogMiddleware() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		log.Println("before handler, extract response.")

		ctx.Next()

		log.Println("after handler, record request/response here.")
	}
}

// 认证的中间件
func authRequiredMiddleware() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		skipedPath := "/app/v1/add"
		path := ctx.Request.URL.Path
		// auth only exclude user add api
		if path != skipedPath {
			// 具体业务逻辑
		}

		ctx.Next()
	}
}

// 中间件
func myMiddleware() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		log.Println("balabala...")
		ctx.Next()
	}
}

上面的demo包含的中间件类型主要是三种:

  • 1.全局中间件
  • 2.组内中间件
  • 3.单个路由中间件

接下来按此顺序顺着源码看看。

1.1 全局中间件

在创建了gin的engine后,我们就可以通过engine.Use()方法添加中间件,以下是engine.Use()方法源码。

// Use attaches a global middleware to the router. i.e. the middleware attached through Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware.
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
	// 调用RouterGroup的Use()方法
	engine.RouterGroup.Use(middleware...)
	...
	return engine
}

从源码看到,实际用engine.Use()方法,其实也是调用RouterGroup.Use()方法,从入参来看,传入的中间件其实也是可变的middleware,即可以一个一个middleware传入,也可以切片传入。

全局中间件注册以后,对全局路由都适用,通常我们会在全局上添加一些如请求耗时、日志记录、panic-recovery的中间件,方便记录与恢复整体service。

1.2 组内中间件

源码:

// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}

源码处理也不复杂,直接将添加的组内路由加到已有中间件的尾部,这里需要注意的地方是,如果是组内路由,在group的时候,其实会新建个group的,所以组内添加的中间件只对组内适用,来看看RouterGroup.Group()方法源码。

// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.
// For example, all the routes that use a common middleware for authorization could be grouped.
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
   return &RouterGroup{
	  // 复制一份全局的中间件handlers,即处理后的handlers = 全局中间件+组内中间件
      Handlers: group.combineHandlers(handlers),
	  // 计算根path
      basePath: group.calculateAbsolutePath(relativePath),
	  // 引用同一个engine 实例
      engine:   group.engine,
   }
}

上面方法是在Group()方法中添加中间件,涉及的中间中间件处理就是复制一份全局中间件的handlers,加上添加的组内中间件handlers,在新建的RouterGroup实例中设置handlers = 全局中间件+组内中间件。

当然,在RouterGroup中也提供`Use()`方法,我们来看看。

// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}

可以看到,其实就是在组内的现有的中间件handlers的基础上,再加上新添加的handlers,这个组内可以认为是全局的范围,也可以认为是确实就是组内的范围,取决于是否新建了组路由,默认下就是全局的,新建了组路由就是组内的,依然很简单不是。

1.3 单个路由中间件

单个路由中间件,仅适用于该路由,为什么这样说,且看源码。

还以demo为例,我们在添加"/ping"路由方法为GET,看源码:

// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
	// 调用handle()方法
	return group.handle(http.MethodGet, relativePath, handlers)
}

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
	absolutePath := group.calculateAbsolutePath(relativePath)
	// 组内中间件handlers + 添加的handlers(最后一个为真正的业务逻辑处理的控制器handler)
	handlers = group.combineHandlers(handlers)
	// 添加路由信息
	group.engine.addRoute(httpMethod, absolutePath, handlers)
	return group.returnObj()
}

根据之前在路由注册中学习,我们知道,注册路由时,实际gin是将路由信息添加到方法树中。

做个小结:

  • 全局中间件

gin的engine实例维护一个全局的handlers,即:engine.RouterGroup.Handlers,添加路由时把这个加到路由的handlers,然后插入到路由存储数据结构方法树中。

  • 组内中间件

每次新建组路由,都会新建个RouterGroup实例,将engine维护的handlers(全局中间件)复制一份,然后加上组内路由,组建成一个新的handlers,即:engine.RouterGroup.Handlers + handlers(组路由上添加的中间件),待创建具体的路由时处理。

  • 单个路由中间件

新建具体的路由时,在路由中添加中间件handlers,会加上group.Handlers,即该路由对应的handlers = group.Handlers + handlers(单个路由添加的handlers),每个路由的handlers都是如此处理,实际注册路由即加到方法树中。举个例子,group.Handlers = Handlers,路由1:path-1,添加的handlers为handlers_1,则插入到对应方法树中的路由信息应该是:path-1 和 Handlers + handlers_1

总体来说,gin对中间件的处理比较简单,不管是全局中间件、组内中间件、单个路由中间件,最终都是要插入到方法树中维护路由信息,engine在新建实例时,就创建了9棵methodTree。为什么是9呢,因为http method就是get/post/put/patch/head/options/delete/connect/trace,一共9个方法。

标签:engine,group,handlers,中间件,源码,gin,路由
From: https://www.cnblogs.com/davis12/p/16969368.html

相关文章