深圳建网站 (报价),成都网站建设:思乐科技,邯郸网站建设品牌加盟,安平县英文网站建设四、组成
前面的文章中#xff0c;我们介绍了其中一部分组成#xff0c;接下来继续学习#xff1a; Router#xff08;路由器#xff09; Gin 使用基于树结构的路由机制来处理 HTTP 请求。它支持动态路由参数、分组路由以及中间件。路由器负责将请求路径映射到相应的处理…四、组成
前面的文章中我们介绍了其中一部分组成接下来继续学习 Router路由器 Gin 使用基于树结构的路由机制来处理 HTTP 请求。它支持动态路由参数、分组路由以及中间件。路由器负责将请求路径映射到相应的处理函数。 Context上下文 gin.Context 是 Gin 中最重要的结构之一它在请求生命周期内传递信息。Context 提供了对请求和响应对象的访问以及用于存储数据、设置状态码、返回 JSON 等方法。 Middleware中间件 中间件是可以在请求被最终处理之前或之后执行的一段代码用于实现日志记录、错误恢复、认证等功能。Gin 支持全局中间件和特定路由组或单个路由使用的中间件。 Handlers处理函数 处理函数是实际执行业务逻辑的位置每个路由都会关联一个或多个处理函数。这些函数接收 gin.Context 参数通过它们可以获取请求数据并生成响应。 Error Handling错误处理 Gin 提供了一种机制来捕获和管理应用程序中的错误可以通过 Context 的方法进行错误报告和恢复操作。 Rendering and Responses渲染与响应 支持多种格式的数据输出包括 JSON、XML 和 HTML 渲染等方便客户端消费不同类型的数据格式。 Binding and Validation绑定与验证 自动将 HTTP 请求中的数据绑定到结构体并支持对输入数据进行验证以确保其符合预期格式和规则。 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 /test24.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 的基本概念 HandlerFunc 类型 在 Gin 中所有的处理函数都必须符合 gin.HandlerFunc 类型。gin.HandlerFunc 被定义为type HandlerFunc func(*Context)这意味着任何接受一个指向 *gin.Context 参数且无返回值的函数都可以作为一个合法的 Handler。 Context 对象 每个 Handler 都会接收到一个 *gin.Context 对象它封装了请求和响应的信息。开发者可以通过这个对象来获取请求数据如查询参数、表单数据、JSON 数据等、设置响应状态码和内容以及控制请求流转如调用 Next() 或 Abort()。
4.4.2 使用
我们在前面的代码例子中无论是绑定在路由上还是作为中间件使用都是这个HandlerFunc类型
Handlers 通常与特定路由绑定在一起通过 HTTP 方法如 GET, POST以及路径进行匹配。也可以多个 Handlers 可以组成中间件链每个处理中步骤按顺序执行。匿名函数或命名函数皆可用作 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 errors 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: 手动写入错误24.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