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  error
	Type ErrorType
	Meta 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中不可预期的是panic
		if 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"

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/938641.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于注意力的几何感知的深度学习对接模型 GAABind - 评测

GAABind 作者是苏州大学的生物基础与医学院, 期刊是 Briefings in Bioinformatics, 2024, 25(1), 1–14。GAABind 是一个基于注意力的几何感知蛋白-小分子结合模式与亲和力预测模型,可以捕捉小分子和蛋白的几何、拓扑结构特征以及相互作用。使用 PDBBind2020 和 CASF2016 作…

【CSS in Depth 2 精译_080】 13.1:CSS 渐变效果(中)——不同色彩空间的颜色插值算法在 CSS 渐变中的应用

当前内容所在位置(可进入专栏查看其他译好的章节内容) 第四部分 视觉增强技术 ✔️【第 13 章 渐变、阴影与混合模式】 ✔️ 13.1 渐变 ✔️ 13.1.1 使用多个颜色节点(上)13.1.2 颜色插值方法(中) ✔️13.1…

虚拟机VirtualBox安装最新版本Oracle数据库

https://www.oracle.com/database/technologies/databaseappdev-vm.html 如上所示,从Oracle官方网站上下载最新版本的VirtualBox虚拟机对应的Oracle数据库安装源文件。 如上所示,在VirtualBox中导入下载的Oracle安装源文件。 如上所示,导入…

热更新解决方案4——xLua热补丁

概述 运行时不在执行C#中的代码,而是执行Lua中的代码,相当于是打了个补丁。 1.第一个热补丁 2.多函数替换 3.协程函数替换 在原HotfixMain脚本中只加个协程函数即可(和在Start中启动协程函数) 4.索引器和属性替换 在HotfixMain中…

突破长链视觉推理瓶颈:Insight-V多智能体架构解析

GitHub 仓库:https://github.com/dongyh20/Insight-V HuggingFace 模型库:https://huggingface.co/THUdyh/Insight-V arXiv 技术论文:https://arxiv.org/pdf/2411.14432 模型:https://huggingface.co/THUdyh/Insight-V-Reason 今天…

IDEA 未启用lombok插件的Bug

项目中maven已引用了lombok依赖,之前运行没有问题的,但有时启动会提示: java: You arent using a compiler supported by lombok, so lombok will not work and has been disabled. Your processor is: com.sun.proxy.$Proxy8 Lombok support…

AI工具如何深刻改变我们的工作与生活

在当今这个科技日新月异的时代,人工智能(AI)已经从科幻小说中的概念变成了我们日常生活中不可或缺的一部分。从智能家居到自动驾驶汽车,从医疗诊断到金融服务,AI正以惊人的速度重塑着我们的世界。 一、工作方式的革新…

压力测试Jmeter简介

前提条件:要安装JDK 若不需要了解,请直接定位到左侧目录的安装环节。 1.引言 在现代软件开发中,性能和稳定性是衡量系统质量的重要指标。为了确保应用程序在高负载情况下仍能正常运行,压力测试变得尤为重要。Apache JMeter 是一…

手眼标定工具操作文档

1.手眼标定原理介绍 术语介绍 手眼标定:为了获取相机与机器人坐标系之间得位姿转换关系,需要对相机和机器人坐标系进行标定,该标定过程成为手眼标定,用于存储这一组转换关系的文件称为手眼标定文件。 ETH:即Eye To …

vue 自定义组件image 和 input

本章主要是介绍自定义的组件:WInput:这是一个验证码输入框,自动校验,输入完成回调等;WImage:这是一个图片展示组件,集成了缩放,移动等操作。 目录 一、安装 二、引入组件 三、使用…

CTFHUB-web(SSRF)

内网访问 点击进入环境,输入 http://127.0.0.1/flag.php 伪协议读取文件 /?urlfile:///var/www/html/flag.php 右击查看页面源代码 端口扫描 1.根据题目提示我们知道端口号在8000-9000之间,使用bp抓包并进行爆破 POST请求 点击环境,访问flag.php 查看页…

Mysql 深度分页查询优化

Mysql 分页优化 1. 问题根源 问题: mysql在数据量大的时候,深度分页数据偏移量会增大,导致查询效率越来越低。 问题根源: 当使用 LIMIT 和 OFFSET 进行分页时,MySQL 必须扫描 OFFSET LIMIT 行,然后丢弃前…

[LeetCode-Python版]21. 合并两个有序链表(迭代+递归两种解法)

题目 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1: 输入:l1 [1,2,4], l2 [1,3,4] 输出:[1,1,2,3,4,4] 示例 2: 输入:l1 [], l2 [] 输出&#x…

Git 安装教程

Git 是一个分布式版本控制系统,用于跟踪源代码的变化。它允许多个开发者协作开发同一个项目,能够有效管理项目的版本历史,便于协作与代码回溯。 Git官网 官网提供各种操作系统的安装程序。 step1.点击"Download for Windows"按钮&a…

Spring学习笔记-基础

前言:我是在哔哩哔哩上黑马程序员上找的课程。-----2024-12-16 官网Spring | Homehttps://spring.io/ Sping全家桶中重要三个: Spring Framework底层框架,在整个全家通中,所有的技术依赖它执行。 Spring Boot简化开发加速开发…

CNAS-AL06《实验室认可领域分类》修订,软件测试领域整体修订

为了不断适应行业发展的需要,进一步完善认可评审管理工作,进一步提高认可评审工作质量,CNAS认可委针对CNAS-AL06《实验室认可领域分类》进行了修订,并于近日正式发布。 原文件CNAS-AL06:20220101有25项一级代码,其中0…

单片机原理及应用笔记:单片机中断系统原理与项目实践

高金鹏:男,银川科技学院计算机与人工智能学院,2022级别计算机科学与技术本科生,单片机原理及应用课程第六组。 指导教师:王兴泽 电子邮件:高金鹏3535558665qq.com 个人CSDN:暴躁的海绵宝宝 暴躁的海绵宝…

【win10+RAGFlow+Ollama】搭建本地大模型助手(教程+源码)

一、RAGFlow简介 RAGFlow是一个基于对文档深入理解的开源RAG(Retrieval-augmented Generation,检索增强生成)引擎。 主要作用: 让用户创建自有知识库,根据设定的参数对知识库中的文件进行切块处理,用户向大…

在 Ubuntu 上部署 Terraform 管理平台:实现云基础设施的集中管理

简介 Terraform 是一款开源基础架构自动化工具,可让您通过命令行界面部署和管理数百台服务器。使用 Terraform,你可以通过在一个人类可读的文件中定义配置来构建、更改和管理你的基础架构。它支持许多云提供商,如 AWS、Azure、GCP 和阿里巴巴…

概率论得学习和整理25:EXCEL 关于直方图/ 频度图 /hist图的细节,2种做hist图的方法

目录 1 hist图的特点 2 hist的设置技巧:直接生成的hist图往往很奇怪不好用:因为横轴的分组不对 3 如何修改分组 4 设置开放边界,把长尾合并,得到hist图1 5 用原始表得到频数表 6 用上面的频数图做柱状图,再修改&…