【GO基础学习】Gin 框架中间件的详解

ops/2024/12/28 11:56:27/

文章目录


中间件详解

Gin 框架中间件是其核心特性之一,主要用于对 HTTP 请求的处理进行前置后置的逻辑插入,例如日志记录、身份认证、错误处理等。

我们在创建默认的gin引擎时:r := gin.Default()

go">func Default() *Engine {debugPrintWARNINGDefault()engine := New()engine.Use(Logger(), Recovery())  // 默认注册的两个中间件return engine
}

通过Use()函数注册了Logger中间件Recovery中间件,Use()函数:

go">// 也就是说,通过 Use() 连接的中间件将被
// 包含在每个请求的处理程序链中。即使是 404、405、静态文件...
// 例如,这里适合放置日志记录器或错误管理中间件
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {engine.RouterGroup.Use(middleware...)engine.rebuild404Handlers()engine.rebuild405Handlers()return engine
}

实际上还是调用的RouterGroup的Use函数:

go">// 使用将中间件添加到组中。
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {group.Handlers = append(group.Handlers, middleware...)return group.returnObj()
}

注册中间件其实就是将中间件函数追加到group.Handlers中。

go">type RouterGroup struct {Handlers HandlersChainbasePath stringengine   *Engineroot     bool
}type HandlersChain []HandlerFunc// HandlerFunc 定义了 gin 中间件作为返回值使用的处理程序。
type HandlerFunc func(*Context)

跟踪这个结构找到HandlersChain ,这是 Gin 中保存中间件和处理函数的核心数据结构,中间件和最终处理函数都以 HandlerFunc 的形式存储在链中。

Context 是 Gin 中非常重要的对象,负责在中间件之间传递数据:

go">type Context struct {Request        *http.Request    // 当前的 HTTP 请求Writer         http.ResponseWriter // 响应输出handlers       HandlersChain    // 当前请求的中间件index          int8             // 当前执行到的中间件索引Keys           map[string]any   // 自定义上下文数据存储// 其他字段...
}

核心字段

  • handlers: 保存当前路由的中间件链。
  • index: 标记当前执行到第几个中间件
  • Keys: 用于存储自定义的上下文数据,在中间件之间共享。

注册路由时,会将对应路由的函数和之前的中间件函数结合到一起:
handlers = group.combineHandlers(handlers) // 将处理请求的函数与中间件函数结合


中间件执行

Gin 中间件分为全局中间件和路由级中间件两种,最终都保存在 HandlersChain 中。

全局中间件

全局中间件通过 Engine.Use() 方法注册,作用于所有路由。

  1. 调用 Use() 方法,将中间件追加到 Engine 的默认 handlers 中。
  2. 在每次请求处理时,默认中间件会首先执行。
go">func (group *RouterGroup) Use(middleware ...HandlerFunc) *RouterGroup {group.Handlers = append(group.Handlers, middleware...) // 注册中间件return group
}r := gin.Default() // gin.Default() 内置了 Logger 和 Recovery 全局中间件

路由级中间件

路由级中间件通过在特定路由上链式调用 Use() 方法注册。

  1. 路由级中间件注册时,保存在当前路由的 HandlersChain 中。
  2. 在匹配到具体路由时,这些中间件会被加入到 Contexthandlers 中,并按顺序执行。
go">func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {handlers = group.combineHandlers(handlers) // 合并路由级中间件和处理函数group.engine.addRoute("GET", path, handlers)return group.returnObj()
}

运行流程

中间件的链式执行

Gin 使用 Context 中的 index 字段来控制中间件的链式调用:

  • index 表示当前执行到的中间件索引。
  • 每个中间件需要调用 c.Next() 才能执行下一个中间件
  • 如果某个中间件不调用 c.Next(),后续的中间件和处理函数将不会执行(终止流程)。
go">func (c *Context) Next() {c.index++for c.index < int8(len(c.handlers)) {c.handlers[c.index](c) // 执行下一个中间件或处理函数c.index++}
}

通过索引遍历HandlersChain链条,从而实现依次调用该路由的每一个函数(中间件或处理请求的函数)。

以一个请求路径为例,假设 /hello 路径注册了两个中间件和一个处理函数:

go">r.GET("/hello", middleware1, middleware2, finalHandler)

执行顺序如下:

  1. 进入 middleware1
  • 如果调用 c.Next(),继续执行下一个中间件
  1. 进入 middleware2
  • 如果调用 c.Next(),执行 finalHandler
  1. 返回时按链路逆序执行剩余代码。

中断流程

某个中间件不调用 c.Next()

go">func middleware1(c *gin.Context) {c.JSON(401, gin.H{"error": "unauthorized"})c.Abort() // 停止后续中间件执行
}
  • 调用 c.Abort() 会终止后续中间件或处理函数的执行。
  • 标志位 c.index 被设置为最大值。

代码示例

  1. 全局中间件
go">package mainimport ("github.com/gin-gonic/gin"
)func Logger() gin.HandlerFunc {return func(c *gin.Context) {// 执行前fmt.Println("Logger - Before Request")c.Next() // 执行下一个中间件// 执行后fmt.Println("Logger - After Request")}
}func main() {r := gin.New()// 注册全局中间件r.Use(Logger())r.GET("/hello", func(c *gin.Context) {c.String(200, "Hello World")})r.Run(":8080")
}

执行流程:

Logger - Before Request
Hello World
Logger - After Request
  1. 路由级中间件
go">package mainimport ("github.com/gin-gonic/gin"
)func Middleware1() gin.HandlerFunc {return func(c *gin.Context) {fmt.Println("Middleware1 - Before")c.Next()fmt.Println("Middleware1 - After")}
}func Middleware2() gin.HandlerFunc {return func(c *gin.Context) {fmt.Println("Middleware2 - Before")c.Next()fmt.Println("Middleware2 - After")}
}func main() {r := gin.New()r.GET("/test", Middleware1(), Middleware2(), func(c *gin.Context) {fmt.Println("Final Handler")c.String(200, "OK")})r.Run(":8080")
}

执行流程:

Middleware1 - Before
Middleware2 - Before
Final Handler
Middleware2 - After
Middleware1 - After
  1. 流程中断
go">func AuthMiddleware() gin.HandlerFunc {return func(c *gin.Context) {token := c.GetHeader("Authorization")if token == "" {c.JSON(401, gin.H{"error": "unauthorized"})c.Abort() // 中断流程return}c.Next()}
}r.GET("/secure", AuthMiddleware(), func(c *gin.Context) {c.String(200, "Secure Data")
})

gin_277">gin框架总结

Engine 是 Gin 框架的核心数据结构,负责路由注册、HTTP 请求处理,以及中间件的全局管理。

go">type Engine struct {RouterGroup            // 继承了 RouterGroup,用于路由分组和中间件管理handlers     HandlersChain // 全局中间件methodTrees  methodTrees   // 路由树存储,不同 HTTP 方法对应一棵 Radix 树ContextPool  sync.Pool     // 上下文对象池,复用 Context 提高性能
}
  1. RouterGroup:
    • 用于路由分组。
    • 支持分组级别的中间件注册。
  2. methodTrees:
    • 保存路由信息的核心,methodTrees 是一组 Radix 树,不同 HTTP 方法(如 GET、POST)对应一棵独立的路由树。
    • 每个树节点存储路由路径的部分信息,并链接到处理函数。
  3. handlers:
  4. ContextPool:
    • 通过对象池复用 Context,减少内存分配和垃圾回收的开销。

路由树构建:methodTrees 是路由信息存储的核心结构,不同 HTTP 方法各自维护一棵 Radix 树。

go">type methodTrees []methodTreetype methodTree struct {method string // HTTP 方法,如 GET、POSTroot   *node  // Radix 树的根节点
}
  • Gin 在注册路由时,使用路径分段递归构建 Radix 树。

  • 每个路由的处理函数和中间件保存在树节点的 handlers 字段中。

go">func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {// 找到对应的 Radix 树root := engine.methodTrees.get(method).rootroot.addRoute(path, handlers)
}

RouterGroup 用于路由的分组和中间件管理,通过分组可以为一组路由统一添加中间件或前缀。

go">type RouterGroup struct {Handlers HandlersChain // 路由分组级中间件basePath string        // 分组路径前缀engine   *Engine       // 指向顶层的 Engine
}
  • 分组路由:通过 Group() 方法创建子分组,支持嵌套分组。
  • 注册中间件:分组级中间件通过 Use() 方法注册,作用范围为该分组及其子分组。

HandlersChain 是一个中间件链表,保存所有的中间件和处理函数,按顺序执行。

go">type HandlersChain []HandlerFunctype HandlerFunc func(*Context)
  • 接收 *Context 参数,在 HTTP 请求的生命周期中共享数据。

  • 可通过调用 c.Next() 继续执行下一个中间件

Context 是 Gin 中最重要的组件之一,用于在中间件和处理函数之间传递数据。

go">type Context struct {Request        *http.Request    // 当前请求Writer         http.ResponseWriter // 当前响应handlers       HandlersChain    // 当前中间件index          int8             // 当前中间件执行位置Keys           map[string]any   // 用于存储用户自定义数据// 其他字段...
}

中间件链控制

  • 调用 c.Next() 执行下一个中间件
  • 调用 c.Abort() 中断执行链。

响应输出

  • 通过 c.JSON()c.String() 等方法生成 HTTP 响应。

自定义数据存储

  • c.Set()c.Get() 用于在中间件间共享数据。

Gin 的中间件机制非常灵活,支持全局、分组、路由级中间件

总的流程:

  1. 路由匹配
  • 根据 HTTP 方法,从 methodTrees 中选择对应的 Radix 树。
  • 使用路径递归查找匹配的节点。
  1. 中间件执行
  1. 处理函数执行
  • 执行完所有中间件后,最终调用路由处理函数。
  1. 返回响应
  • 通过 Context 的方法生成 HTTP 响应。


http://www.ppmy.cn/ops/145647.html

相关文章

AT6668-6N-22:BDS定位SOC芯片,常用在车载系统

杭州中科微AT6668-6N-22仅支持单北斗系统&#xff0c;北斗二号和三号&#xff0c;频点B1IB1C&#xff0c;不支持其他导航系统&#xff0c;工作温度在-40~85C。 关于AT6668 杭州中科微AT6668是针对卫星导航信号芯片市场设计的可以支持同时接受多个卫星导航系统的卫星信号&#x…

TLS协议详解-基础概念

文章目录 前言一、TLS基础SSL/TLS 发展史加密套件&#xff08;cipher suite&#xff09;openssl对称加密加密分组非对称加密摘要算法数字签名数字证书和CA小结 前言 通讯过程中具备四个特效&#xff0c;才可以任务是“安全”的&#xff0c;这四个特性是&#xff1a; 机密性 完…

HarmonyOS Next 应用元服务开发-分布式数据对象迁移数据文件资产迁移

文件资产迁移&#xff0c;对于图片、文档等文件类数据&#xff0c;需要先将其转换为资产commonType.Asset类型&#xff0c;再封装到分布式数据对象中进行迁移。迁移实现方式与普通的分布式数据对象类似&#xff0c;下面仅针对差异部分进行说明。 在源端&#xff0c;将需要迁移…

linux内核如何实现TCP的?

TCP(传输控制协议)是网络通信中的核心协议之一,实现了可靠的、面向连接的、基于字节流的通信。在Linux内核中,TCP的实现相对复杂,涉及多个模块和层次。以下是一些关键概念和机制: 1. 协议栈 Linux 内核中的网络协议栈(Network Stack)是分层设计的,包括链路层、网络层…

信号仿真高级工程师面试题

信号仿真高级工程师面试题可能涵盖多个方面,旨在全面评估应聘者的专业知识、技能水平、实践经验和问题解决能力。以下是一些可能的面试题及其简要解析: 一、专业知识与技能 描述你对信号仿真的理解 考察点:对信号仿真基本概念、原理及应用的掌握程度。参考答案:信号仿真是…

Hadoop集群(HDFS集群、YARN集群、MapReduce​计算框架)

一、 简介 Hadoop主要在分布式环境下集群机器&#xff0c;获取海量数据的处理能力&#xff0c;实现分布式集群下的大数据存储和计算。 其中三大核心组件: HDFS存储分布式文件存储、YARN分布式资源管理、MapReduce分布式计算。 二、工作原理 2.1 HDFS集群 Web访问地址&…

通过交叉实现数据触底分页效果new IntersectionObserver()(html、react、vue2、vue3)中使用

react中用法 import React, { useState, useEffect, useRef } from react;const InfiniteScroll () > {const [items, setItems] useState([]);const [loading, setLoading] useState(false);const [page, setPage] useState(1);const loaderRef useRef(null);// 模拟…

FreeType矢量字符库的介绍、交叉编译以及安装

FreeType矢量字符库的介绍 FreeType 是一个开源的跨平台字体引擎库&#xff0c;广泛用于 Linux 嵌入式系统中实现字符显示的功能。它提供了高效的 TrueType、OpenType 和其他字体格式的解析和渲染功能&#xff0c;在嵌入式开发中尤其适合用来绘制矢量字体和位图字体。 FreeTy…