当前位置: 首页 > news >正文

21 go语言(golang) - gin框架安装及使用(二)

四、组成

前面的文章中,我们介绍了其中一部分组成,接下来继续学习:

  1. Router(路由器)

    • Gin 使用基于树结构的路由机制来处理 HTTP 请求。它支持动态路由参数、分组路由以及中间件。
    • 路由器负责将请求路径映射到相应的处理函数。
  2. Context(上下文)

    • gin.Context 是 Gin 中最重要的结构之一,它在请求生命周期内传递信息。
    • Context 提供了对请求和响应对象的访问,以及用于存储数据、设置状态码、返回 JSON 等方法。
  3. Middleware(中间件)

    • 中间件是可以在请求被最终处理之前或之后执行的一段代码,用于实现日志记录、错误恢复、认证等功能。
    • Gin 支持全局中间件和特定路由组或单个路由使用的中间件。
  4. Handlers(处理函数)

    • 处理函数是实际执行业务逻辑的位置,每个路由都会关联一个或多个处理函数。
    • 这些函数接收 gin.Context 参数,通过它们可以获取请求数据并生成响应。
  5. Error Handling(错误处理)

    • Gin 提供了一种机制来捕获和管理应用程序中的错误,可以通过 Context 的方法进行错误报告和恢复操作。
  6. Rendering and Responses(渲染与响应)

    • 支持多种格式的数据输出,包括 JSON、XML 和 HTML 渲染等,方便客户端消费不同类型的数据格式。
  7. Binding and Validation(绑定与验证)

    • 自动将 HTTP 请求中的数据绑定到结构体,并支持对输入数据进行验证,以确保其符合预期格式和规则。
  8. Templates (模板)

    • 虽然不是框架核心,但 Gin 支持集成 HTML 模板引擎,用于生成动态网页内容。

4.3 Middleware(中间件)

4.3.4 中间件的调用顺序

上一章讲的中间件,还有部分内容我们先来看看

4.3.4.1 按注册顺序执行

中间件会按照它们被添加到 Gin 实例上的顺序依次执行

func Test1(t *testing.T) {r := gin.Default()r.Use(func(c *gin.Context) {fmt.Println("func1 ...")})r.Use(func(c *gin.Context) {fmt.Println("func2 ...")})r.GET("/test1", func(c *gin.Context) {fmt.Println("test1 最终路由方法。。。")})r.Use(func(c *gin.Context) {fmt.Println("func3 ...")})r.GET("/test2", func(c *gin.Context) {fmt.Println("test2 最终路由方法。。。")})r.Run()
}

依次调用test1和test2路由,输出

func1 ...
func2 ...
test1 最终路由方法。。。
[GIN] 2024/12/13 - 15:11:44 | 200 |      24.436µs |             ::1 | GET      "/test1"
func1 ...
func2 ...
func3 ...
test2 最终路由方法。。。
[GIN] 2024/12/13 - 15:11:48 | 200 |      21.672µs |             ::1 | GET      "/test2"
4.3.4.2 c.Next()控制顺序

c.Next() 是 Gin 框架中 *gin.Context 类型的方法,用于控制中间件链的执行流程。

  • 当一个中间件调用 c.Next() 时,它将暂停当前处理中函数的执行,并将控制权交给下一个处理中函数或最终的路由处理器。
  • 多个中间件通过调用 c.Next() 可以形成嵌套结构,类似于栈。当所有后续步骤完成后,程序会回到先前上下文继续执行剩余代码。即返回时则是相反顺序(即“先进后出”)。

可以理解为,将原本顺序执行的后续中间件,嵌套在c.Next()中先执行

func Test2(t *testing.T) {r := gin.Default()r.Use(func(c *gin.Context) {fmt.Println("func1 begin...")//c.Next()fmt.Println("func1 end...")})r.Use(func(c *gin.Context) {fmt.Println("func2 begin...")//c.Next()fmt.Println("func2 end...")})r.Use(func(c *gin.Context) {fmt.Println("func3 begin...")//c.Next()fmt.Println("func3 end...")})r.GET("/test_next", func(c *gin.Context) {fmt.Println("test_next 最终路由方法。。。")})r.Run()
}

当注释掉所有c.Next()时,顺序执行

func1 begin...
func1 end...
func2 begin...
func2 end...
func3 begin...
func3 end...
test_next 最终路由方法。。。

打开func1的c.Next()时,后续的中间件和最终路由就全部在c.Next()中执行完,最后才返回func1继续执行,即最后再打印func1 end…

func1 begin...
func2 begin...
func2 end...
func3 begin...
func3 end...
test_next 最终路由方法。。。
func1 end...

再打开func2的c.Next()时,道理相同,c.Next()中嵌套c.Next()

func1 begin...
func2 begin...
func3 begin...
func3 end...
test_next 最终路由方法。。。
func2 end...
func1 end...

再打开func3的c.Next()时

func1 begin...
func2 begin...
func3 begin...
test_next 最终路由方法。。。
func3 end...
func2 end...
func1 end...
4.3.4.3 c.Abort()控制顺序

c.Abort() 是 Gin 框架中 *gin.Context 类型的方法,用于立即停止当前请求的进一步处理。

  • 一旦调用 c.Abort(),当前请求将不再继续传递给下一个处理中函数或最终的路由处理器。
  • 这意味着所有在调用点之后注册的中间件和路由处理器都不会被执行。
  • 调用 c.Abort() 会设置一个内部标志位,指示该请求已经被终止。这个标志可以通过 c.IsAborted() 方法检查。
  • 在某些情况下,例如认证失败、权限不足或其他需要立即返回响应的情况,可以使用 c.Abort() 来阻止进一步操作,并直接返回适当的响应给客户端。
func Test3(t *testing.T) {r := gin.Default()r.Use(func(c *gin.Context) {fmt.Println("func1 begin...")fmt.Println("func1 end...")})r.Use(func(c *gin.Context) {fmt.Println("func2 begin...")fmt.Println(c.IsAborted())c.Abort() // 从此中间件开始,后续的中间件包括最终路由都不再执行fmt.Println(c.IsAborted())fmt.Println("func2 end...") // 但是这行代码会执行})r.Use(func(c *gin.Context) {fmt.Println("func3 begin...")fmt.Println("func3 end...")})r.GET("/test_next", func(c *gin.Context) {fmt.Println("test_next 最终路由方法。。。")c.JSON(200, gin.H{"success": "true",})})r.Run()
}

输出:

func1 begin...
func1 end...
func2 begin...
false
true
func2 end...
4.3.4.3 Next 和 Abort 共同控制?

注:如果Next()中有Abort(),执行顺序是怎样的?

func Test4(t *testing.T) {r := gin.Default()r.Use(func(c *gin.Context) {fmt.Println("func1 begin...")c.Next()fmt.Println("func1 end...")})r.Use(func(c *gin.Context) {fmt.Println("func2 begin...")c.Abort()fmt.Println("func2 end...")})r.Use(func(c *gin.Context) {fmt.Println("func3 begin...")fmt.Println("func3 end...")})r.GET("/test_next", func(c *gin.Context) {fmt.Println("test_next 最终路由方法。。。")c.JSON(200, gin.H{"success": "true",})})r.Run()
}

输出:

func1 begin...
func2 begin...
func2 end...
func1 end...

可以看到,c.Next()还是会执行完,并返回后,打印func1 end...c.Abort()只是控制切断了func3的执行,这里很坑,问了很多AI,回答的都是func1 end...不会执行,要自己试过才知道

4.4 Handlers(处理函数)

处理函数(Handlers)是处理 HTTP 请求的核心组件。它们负责接收请求、执行业务逻辑,并返回响应。

4.4.1 Handlers 的基本概念

  1. HandlerFunc 类型

    • 在 Gin 中,所有的处理函数都必须符合 gin.HandlerFunc 类型。
    • gin.HandlerFunc 被定义为:type HandlerFunc func(*Context)
    • 这意味着任何接受一个指向 *gin.Context 参数且无返回值的函数都可以作为一个合法的 Handler。
  2. Context 对象

    • 每个 Handler 都会接收到一个 *gin.Context 对象,它封装了请求和响应的信息。
    • 开发者可以通过这个对象来获取请求数据(如查询参数、表单数据、JSON 数据等)、设置响应状态码和内容,以及控制请求流转(如调用 Next()Abort())。

4.4.2 使用

我们在前面的代码例子中,无论是绑定在路由上,还是作为中间件使用,都是这个HandlerFunc类型

  1. Handlers 通常与特定路由绑定在一起,通过 HTTP 方法(如 GET, POST)以及路径进行匹配。
  2. 也可以多个 Handlers 可以组成中间件链,每个处理中步骤按顺序执行。
  3. 匿名函数或命名函数皆可用作 Handler
func myHandler(c *gin.Context) {fmt.Println("命名函数")
}func Test5(t *testing.T) {r := gin.Default()// 使用匿名函数my := func(c *gin.Context) {fmt.Println("匿名函数1")}r.GET("/t1", my)// 使用匿名函数r.GET("/t2", func(c *gin.Context) {fmt.Println("匿名函数2")})// 使用命名函数r.GET("/t3", myHandler)r.Run()
}

4.5 Error Handling(错误处理)

错误处理(Error Handling)帮助开发者在请求处理过程中捕获和管理错误。Gin 提供了一些机制来简化错误的记录、传递和响应。

4.5.1 基本概念

Gin 使用 gin.Error 类型来表示错误。它包含了一个 error 接口,以及一些附加信息:元数据(Meta any)和类型标识(Type ErrorType),其中的错误类型用于标识不同种类的错误,例如绑定错误、渲染错误等。可以通过设置不同的类型来对错误进行分类。

// 结构体的源码:
// Error represents a error's specification.
type Error struct {Err  errorType ErrorTypeMeta any
}

4.5.2 错误处理机制

4.5.2.1 Context 中的 Errors 字段
  • 每个 *gin.Context 对象都有一个 Errors 字段,这是一个存储所有发生在该请求生命周期内的 gin.Error 切片。
  • 开发者可以通过这个字段访问并操作这些累积起来的错误,并根据需要进行进一步操作(如日志记录或响应生成)。
4.5.2.2 添加错误

在处理中函数中,可以使用 c.Error(err) 方法将新的 error 添加到当前上下文中。

r.Use(func(c *gin.Context) {fmt.Println("func1...")c.Error(fmt.Errorf("手动写入错误1"))})
4.5.2.3 检索错误

可以通过迭代上下文中的 Errors 来访问所有已记录过得 errors。

func Test6(t *testing.T) {r := gin.Default()r.Use(func(c *gin.Context) {fmt.Println("func1...")c.Error(fmt.Errorf("手动写入错误1"))})r.Use(func(c *gin.Context) {fmt.Println("func2...")c.Error(fmt.Errorf("手动写入错误2"))})r.GET("/error", func(c *gin.Context) {errors := c.Errors// 遍历所有的错误for _, err := range errors {fmt.Println(err)}fmt.Println("正常返回")})r.Run()
}

输出,其中最后两行的日志是gin自带打印的错误信息

func1...
func2...
手动写入错误1
手动写入错误2
正常返回
[GIN] 2024/12/16 - 10:50:56 | 200 |      34.129µs |       127.0.0.1 | GET      "/error"
Error #01: 手动写入错误1
Error #02: 手动写入错误2

4.5.3 自定义全局异常处理中间件

为了统一管理应用程序中的异常情况,通常会创建一个全局异常处理中间件。在这个中间件里,可以遍历每个请求产生过得 errors 并做出相应反应,比如写入日志系统等。

func myErrorHandler(c *gin.Context) {c.Next() // 执行后续处理中步骤errors := c.Errors// 检查是否有任何errors被注册if len(errors) > 0 {for i, e := range errors {fmt.Println(i, e) // 打印或保存日志信息}// 返回通用响应给客户端c.JSON(500, gin.H{"msg": "内部错误",})}
}func Test7(t *testing.T) {r := gin.Default()r.Use(myErrorHandler)r.GET("/error", func(c *gin.Context) {// 模拟业务执行过程中的错误c.Error(fmt.Errorf("手动写入错误"))c.JSON(200, gin.H{"msg": "成功",})})r.Run()
}

客户端输出,可以看到,返回的有点错乱

{"msg": "成功"
}{"msg": "内部错误"
}

所以,要实现类似spring中全局的错误处理,还需要改进一下

func myErrorHandler2(c *gin.Context) {defer func() {errors := c.Errors// 在golang中,这些错误属于可以预期的错误,可以简单的打印日志或做对应的业务处理,真正类似java中不可预期的是panicif len(errors) > 0 {for i, e := range errors {fmt.Println(i, e) // 打印或保存日志信息}}// 这里处理真正的不可预期的报错hasPanic := recover()if hasPanic != nil {fmt.Println("捕获到异常!!")c.Abort() // 这行代码一定要加,不然后续代码还会执行,比如在最终路由中的打印// 返回通用响应给客户端c.JSON(500, gin.H{"msg": "内部错误",})}}()c.Next() // 执行后续处理中步骤
}func Test8(t *testing.T) {r := gin.Default()r.Use(myErrorHandler2)r.Use(func(c *gin.Context) {// 模拟错误list := []int{1, 2, 3, 4}i := list[5]println(i)})r.GET("/error", func(c *gin.Context) {fmt.Println("正常进入最终路由")c.JSON(200, gin.H{"msg": "成功",})})r.Run()
}

4.5.4 AbortWithError

Gin 提供了便捷方法 AbortWithError(statusCode int , err error ) ,用于同时终止请求并将指定状态码与error对象一起加入到context.errors列表内 。这使得开发者能够快速地结合HTTP协议标准状态码与具体业务逻辑需求来构建更具表达力且一致性强大的API接口 。

func Test9(t *testing.T) {r := gin.Default()r.Use(func(c *gin.Context) {fmt.Println("func1...")})r.Use(func(c *gin.Context) {random := rand.Intn(2)// 模拟随机错误if random == 0 {// 将错误添加到Context中,并且终止后续的调用链c.AbortWithError(500, fmt.Errorf("内部错误"))// 等价于//c.Error(fmt.Errorf("内部错误"))//c.AbortWithStatus(500)return}fmt.Println("func2...")})r.Use(func(c *gin.Context) {fmt.Println("func3...")})r.GET("/error", func(c *gin.Context) {c.JSON(200, "正常")})r.Run()
}

输出

// 异常情况
func1...
[GIN] 2024/12/16 - 16:34:28 | 500 |      18.752µs |       127.0.0.1 | GET      "/error"
Error #01: 内部错误// 正常情况
func1...
func2...
func3...
[GIN] 2024/12/16 - 16:34:30 | 200 |      61.868µs |       127.0.0.1 | GET      "/error"
http://www.lryc.cn/news/506540.html

相关文章:

  • Intel(R) Iris(R) Xe Graphics安装Anaconda、Pytorch(CPU版本)
  • 【Unity3D】实现可视化链式结构数据(节点数据)
  • Three.js推荐-可以和Three.js结合的动画库
  • 增强现实(AR)和虚拟现实(VR)的应用
  • 告别机器人味:如何让ChatGPT写出有灵魂的内容
  • 【Threejs】从零开始(六)--GUI调试开发3D效果
  • Cocos Creator 试玩广告开发
  • 快速解决oracle 11g中exp无法导出空表的问题
  • selenium 报错 invalid argument: invalid locator
  • Flink2.0未来趋势中需要注意的一些问题
  • 机械鹦鹉与真正的智能:大语言模型推理能力的迷思
  • 本地电脑使用命令行上传文件至远程服务器
  • 【系统】Windows11更新解决办法,一键暂停
  • 34. Three.js案例-创建球体与模糊阴影
  • Qt同步读取串口
  • 如何用上AI视频工具Sora,基于ChatGPT升级Plus使用指南
  • 对象的状态变化处理与工厂模式实现
  • 关于IP代理API,我应该了解哪些功能特性?以及如何安全有效地使用它来隐藏我的网络位置?
  • 在Linux上将 `.sh` 脚本、`.jar` 包或其他脚本文件添加到开机自启动
  • [Maven]构建项目与高级特性
  • 【系统架构设计师】真题论文: 论数据分片技术及其应用(包括解题思路和素材)
  • 【bWAPP】XSS跨站脚本攻击实战
  • Ubuntu 22.04,Rime / luna_pinyin.schema 输入法:外挂词库,自定义词库 (****) OK
  • 多协议视频监控汇聚/视频安防系统Liveweb搭建智慧园区视频管理平台
  • 如何高效获取Twitter数据:Apify平台上的推特数据采集解决方案
  • 第二部分:进阶主题 15 . 安全管理 --[MySQL轻松入门教程]
  • 力扣hot100——双指针
  • 【代码随想录day58】【C++复健】 117. 软件构建(拓扑排序);47. 参加科学大会(dijkstra(朴素版)精讲)
  • 【NLP 16、实践 ③ 找出特定字符在字符串中的位置】
  • 费解的开关(bfs + 哈希表 or 递推)