介绍
Gin 是一个用 Go(Golang)编写的 HTTP Web 框架。它具有类似 Martini 的 API,但性能比 Martini 快 40 倍。如果您需要精彩的表演,那就给自己来点杜松子酒吧。
Gin 官网介绍自己是一个具有“高性能”和“良好生产力”的 Web 框架。它还提到了另外两个库。第一个是Martini,它也是一个Web框架,并且有一个酒的名字。 Gin 表示它使用其 API,但速度快了 40 倍。使用httprouter是它能比Martini快40倍的重要原因。
官网的“Features”中列出了八个关键功能,稍后我们将逐步看到这些功能的实现。
- 快
- 中间件支持
- 无崩溃
- JSON 验证
- 路线分组
- 错误管理
- 渲染内置/可扩展
从一个小例子开始
让我们看一下官方文档中给出的最小示例。
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
运行这个例子,然后用浏览器访问http://localhost:8080/ping,就会得到“乒乓”声。
这个例子非常简单。它可以分为三个步骤:
- 使用 gin.Default() 创建具有默认配置的 Engine 对象。
- 在Engine的GET方法中注册“/ping”地址的回调函数。此函数将返回一个“pong”。
- 启动Engine,开始监听端口并提供服务。
HTTP方法
从上面小例子中的GET方法我们可以看出,在Gin中,HTTP方法的处理方法需要使用对应的同名函数进行注册。
HTTP 方法有九种,最常用的四种是 GET、POST、PUT 和 DELETE,分别对应查询、插入、更新和删除四种功能。需要注意的是,Gin还提供了Any接口,可以直接将所有HTTP方法处理方法绑定到一个地址。
返回的结果一般包含两部分或三部分。代码和消息始终存在,数据通常用于表示附加数据。如果没有额外的数据要返回,则可以省略。在示例中,200 是 code 字段的值,“pong”是 message 字段的值。
创建引擎变量
在上面的示例中,gin.Default() 用于创建引擎。然而,这个函数是 New 的包装。其实Engine就是通过New接口创建的。
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
暂时简单看一下创建过程,不要关注Engine结构体中各个成员变量的含义。可以看到,New除了创建并初始化一个Engine类型的引擎变量外,还将engine.pool.New设置为一个调用engine.allocateContext()的匿名函数。这个函数的作用后面会讲。
注册路由回调函数
Engine 中有一个嵌入的 struct RouterGroup。 Engine的HTTP方法相关接口均继承自RouterGroup。官网提到的功能点中的“路由分组”是通过RouterGroup结构体实现的。
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
每个 RouterGroup 都与一个基本路径 basePath 相关联。 Engine 中嵌入的 RouterGroup 的基本路径是“/”。
还有一组处理函数Handlers。所有与该组关联的路径下的请求都会额外执行该组的处理函数,主要用于中间件调用。 Engine创建时Handlers为nil,可以通过Use方法导入一组函数。我们稍后会看到这个用法。
type RouterGroup struct { Handlers HandlersChain // Processing functions of the group itself basePath string // Associated base path engine *Engine // Save the associated engine object root bool // root flag, only the one created by default in Engine is true }
RouterGroup的handle方法是注册所有HTTP方法回调函数的最终入口。初始示例中调用的 GET 方法和其他与 HTTP 方法相关的方法只是对 handle 方法的包装。
handle方法会根据RouterGroup的basePath和相对路径参数计算出绝对路径,同时调用combineHandlers方法得到最终的handlers数组。这些结果作为参数传递给Engine的addRoute方法来注册处理函数。
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
combineHandlers方法的作用是创建一个切片mergedHandlers,然后将RouterGroup本身的Handler复制到其中,然后将参数的handler复制到其中,最后返回mergedHandlers。也就是说,当使用handle注册任何方法时,实际结果包括RouterGroup本身的Handler。
使用Radix Tree加速路由检索
在官网提到的“快速”特征点中,提到网络请求的路由是基于基数树(Radix Tree)实现的。这部分并不是由Gin实现的,而是由一开始介绍Gin时提到的httprouter实现的。 Gin使用httprouter来实现这部分功能。基数树的实现这里暂时不讲。我们现在只关注它的用法。也许稍后我们会单独写一篇文章来介绍基数树的实现。
在引擎中,有一个 trees 变量,它是 methodTree 结构的一个切片。正是这个变量保存了对所有基数树的引用。
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
引擎为每个 HTTP 方法维护一个基数树。这棵树的根节点和方法的名称一起保存在一个methodTree变量中,所有methodTree变量都在树中。
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
可以看到,Engine的addRoute方法中,首先会使用trees的get方法来获取该方法对应的radix树的根节点。如果没有获取到基数树的根节点,则说明之前没有为该方法注册过任何方法,将会创建一个树节点作为树的根节点,并添加到树中。
获取根节点后,使用根节点的addRoute方法注册一组针对路径path的处理函数handler。这一步是为路径和处理程序创建一个节点并将其存储在基数树中。如果你尝试注册一个已经注册的地址,addRoute会直接抛出一个panic错误。
在处理HTTP请求时,需要通过路径找到对应节点的值。根节点有一个getValue方法负责处理查询操作。我们在谈论 Gin 处理 HTTP 请求时会提到这一点。
导入中间件处理函数
RouterGroup的Use方法可以导入一组中间件处理函数。官网提到的功能点中的“中间件支持”是通过Use方法实现的。
在最初的示例中,创建Engine结构体变量时,没有使用New,而是使用了Default。让我们看看 Default 额外做了什么。
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
可以看出,这是一个非常简单的函数。除了调用New创建Engine对象外,只调用Use导入Logger和Recovery两个中间件函数的返回值。 Logger的返回值是用于记录日志的函数,Recovery的返回值是用于处理panic的函数。我们暂时跳过这个,稍后再看这两个函数。
虽然Engine内嵌了RouterGroup,也实现了Use方法,但只是调用了RouterGroup的Use方法以及一些辅助操作。
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
可见RouterGroup的使用方法也非常简单。它只是通过append将参数的中间件处理功能添加到自己的Handler中。
开始跑步
在这个小例子中,最后一步是不带参数调用 Engine 的 Run 方法。调用后,整个框架开始运行,用浏览器访问注册地址即可正确触发回调。
type RouterGroup struct { Handlers HandlersChain // Processing functions of the group itself basePath string // Associated base path engine *Engine // Save the associated engine object root bool // root flag, only the one created by default in Engine is true }
Run方法只做两件事:解析地址和启动服务。这里地址其实只需要传一个字符串就可以了,但是为了达到能传能不能传的效果,使用了一个可变参数。 resolveAddress方法处理addr不同情况的结果。
启动服务使用标准库的net/http包中的ListenAndServe方法。该方法接受一个监听地址和一个Handler接口的变量。 Handler接口的定义非常简单,只有一个ServeHTTP方法。
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
因为Engine实现了ServeHTTP,所以Engine本身将被传递到这里的ListenAndServe方法。当监听端口有新的连接时,ListenAndServe会负责接受并建立连接,当连接上有数据时,会调用handler的ServeHTTP方法进行处理。
处理消息
Engine的ServeHTTP是处理消息的回调函数。我们来看看它的内容。
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len(group.Handlers) + len(handlers) assert1(finalSize回调函数有两个参数。第一个是w,用于接收请求回复。将回复数据写入w。另一个是req,保存本次请求的数据。后续处理所需的所有数据都可以从req中读取
ServeHTTP 方法做了四件事。首先从pool池中获取一个Context,然后将Context与回调函数的参数绑定,然后以Context为参数调用handleHTTPRequest方法来处理这次网络请求,最后将Context放回池中。
我们首先只看handleHTTPRequest方法的核心部分。
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }handleHTTPRequest方法主要做了两件事。首先根据请求的地址从基数树中获取之前注册的方法。这里,handlers会被分配到Context中进行本次处理,然后调用Context的Next函数来执行handlers中的方法。最后将本次请求的返回数据写入Context的responseWriter类型对象中。
语境
处理 HTTP 请求时,所有与上下文相关的数据都在 Context 变量中。作者还在Context结构体的注释中写到“Context is the most important part of gin”,可见其重要性。
上面讲Engine的ServeHTTP方法时可以看出,Context并不是直接创建的,而是通过Engine的pool变量的Get方法获取的。取出后,使用前重置其状态,使用后放回池中。
Engine 的池变量的类型为sync.Pool。目前只知道它是Go官方提供的支持并发使用的对象池。您可以通过 Get 方法从池中获取对象,也可以使用 Put 方法将对象放入池中。当池为空并且使用Get方法时,它会通过自己的New方法创建一个对象并返回。
这个New方法是在Engine的New方法中定义的。我们再看一下Engine的New方法。
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }从代码中可以看出Context的创建方法是Engine的allocateContext方法。 allocateContext 方法并没有什么神秘之处。它只是对切片长度进行两步预分配,然后创建对象并返回它。
type RouterGroup struct { Handlers HandlersChain // Processing functions of the group itself basePath string // Associated base path engine *Engine // Save the associated engine object root bool // root flag, only the one created by default in Engine is true }上面提到的 Context 的 Next 方法将执行处理程序中的所有方法。我们来看看它的实现。
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }虽然handlers是一个切片,但是Next方法并不是简单地实现为handlers的遍历,而是引入了一个处理进度记录索引,该索引初始化为0,在方法开始时递增,在方法结束后再次递增执行完成。
Next的设计和它的用法有很大关系,主要是为了配合一些中间件功能。例如,当某个handler执行过程中触发panic时,可以使用中间件中的recover捕获错误,然后再次调用Next继续执行后续的handler,而不会因为该问题影响整个handlers数组一名处理程序。
应对恐慌
在Gin中,如果某个请求的处理函数触发了panic,整个框架并不会直接崩溃。相反,将抛出错误消息,并且将继续提供服务。这有点类似于Lua框架通常使用xpcall来执行消息处理函数。这个操作就是官方文档中提到的“Crash-free”特性点。
上面提到,使用 gin.Default 创建 Engine 时,会执行 Engine 的 Use 方法来导入两个函数。其中之一是 Recovery 函数的返回值,它是其他函数的包装。最终调用的函数是CustomRecoveryWithWriter。我们来看看这个函数的实现。
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }这里我们不关注错误处理的细节,而只看看它做了什么。该函数返回一个匿名函数。在这个匿名函数中,使用defer注册了另一个匿名函数。在这个内部匿名函数中,使用recover来捕获panic,然后进行错误处理。处理完成后,调用Context的Next方法,这样Context原本按顺序执行的处理程序就可以继续执行。
Leapcell:用于 Web 托管、异步任务和 Redis 的下一代无服务器平台
最后给大家介绍一下部署Gin服务最好的平台:Leapcell。
1. 多语言支持
- 使用 JavaScript、Python、Go 或 Rust 进行开发。
2.免费部署无限个项目
- 只需支付使用费用——无请求,不收费。
3. 无与伦比的成本效益
- 即用即付,无闲置费用。
- 示例:25 美元支持 694 万个请求,平均响应时间为 60 毫秒。
4.简化的开发者体验
- 直观的用户界面,轻松设置。
- 完全自动化的 CI/CD 管道和 GitOps 集成。
- 实时指标和日志记录以获取可操作的见解。
5. 轻松的可扩展性和高性能
- 自动扩展以轻松处理高并发。
- 零运营开销——只需专注于构建。
在文档中探索更多内容!
Leapcell Twitter:https://x.com/LeapcellHQ
以上是深入探讨 Gin:Golang 的领先框架的详细内容。更多信息请关注PHP中文网其他相关文章!

本文解释了GO的软件包导入机制:命名imports(例如导入“ fmt”)和空白导入(例如导入_ fmt; fmt;)。 命名导入使包装内容可访问,而空白导入仅执行t

本文解释了Beego的NewFlash()函数,用于Web应用程序中的页间数据传输。 它专注于使用newflash()在控制器之间显示临时消息(成功,错误,警告),并利用会话机制。 Lima

本文详细介绍了MySQL查询结果的有效转换为GO结构切片。 它强调使用数据库/SQL的扫描方法来最佳性能,避免手动解析。 使用DB标签和Robus的结构现场映射的最佳实践

本文演示了创建模拟和存根进行单元测试。 它强调使用接口,提供模拟实现的示例,并讨论最佳实践,例如保持模拟集中并使用断言库。 文章

本文探讨了GO的仿制药自定义类型约束。 它详细介绍了界面如何定义通用功能的最低类型要求,从而改善了类型的安全性和代码可重复使用性。 本文还讨论了局限性和最佳实践

本文详细介绍了在GO中详细介绍有效的文件,将OS.WriteFile(适用于小文件)与OS.openfile和缓冲写入(最佳大型文件)进行比较。 它强调了使用延迟并检查特定错误的可靠错误处理。

本文使用跟踪工具探讨了GO应用程序执行流。 它讨论了手册和自动仪器技术,比较诸如Jaeger,Zipkin和Opentelemetry之类的工具,并突出显示有效的数据可视化


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

EditPlus 中文破解版
体积小,语法高亮,不支持代码提示功能

安全考试浏览器
Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

螳螂BT
Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

SublimeText3 英文版
推荐:为Win版本,支持代码提示!

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)