有了net/http, 为什么还要有gin
1. 简介
在Go语言中,net/http 包提供了一个强大且灵活的标准HTTP库,可以用来构建Web应用程序和处理HTTP请求。这个包是Go语言标准库的一部分,因此所有的Go程序都可以直接使用它。既然已经有 net/http 这样强大和灵活的标准库,为什么还出现了像 Gin 这样的,方便我们构建Web应用程序的第三方库?
其实在于net/http的定位,其提供了基本的HTTP功能,但它的设计目标是简单和通用性,而不是提供高级特性和便利的开发体验。在处理HTTP请求和构建Web应用时,可能会遇到一系列的问题,这也造就了Gin 这样的第三方库的出现。
下文我们将对一系列场景的介绍,通过比对 net/http 和 Gin 二者在这些场景下的不同实现,进而说明Gin 框架存在的必要性。
2. 复杂路由场景处理
在实际的Web应用程序开发中,使用同一个路由前缀的场景非常普遍,这里举两个比较常见的例子。
比如在设计API时,可能会随着时间的推移对API进行更新和改进。为了保持向后兼容性,并允许多个API版本共存,通常会使用类似 /v1、/v2 这样的路由前缀来区分不同版本的API。
还有另外一个场景,一个大型Web应用程序经常是由多个模块组成,每个模块负责不同的功能。为了更好地组织代码和区分不同模块的路由,经常都是使用模块名作为路由前缀。
在这两个场景中,大概率都会使用同一个路由前缀。如果使用net/http 来框架web应用,实现大概如下:
package main
import (
"fmt"
"net/http"
)
func handleUsersV1(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "User list in v1")
}
func handlePostsV1(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Post list in v1")
}
func main() {
http.HandleFunc("/v1/users", handleUsersV1)
http.HandleFunc("/v1/posts", handlePostsV1)
http.ListenAndServe(":8080", nil)
}
在上面的示例中,我们手动使用 http.HandleFunc 来定义不同的路由处理函数。
代码示例看起来没有太大问题,但是是因为只有两个路由组,如果随着路由数量增加,处理函数的数量也会增加,代码会变得越来越复杂和冗长。而且每一个路由规则都需要手动设置路由前缀,如例子中的 v1 前缀,如果前缀是 /v1/v2/... 这样子设置起来,会导致代码架构不清晰,同时操作繁杂,容易出错。
但是相比之下,Gin 框架实现了路由分组的功能,下面来看Gin 框架来对该功能的实现:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// 创建一个路由组
v1 := router.Group("/v1")
{
v1.GET("/users", func(c *gin.Context) {
c.String(200, "User list in v1")
})
v1.GET("/posts", func(c *gin.Context) {
c.String(200, "Post list in v1")
})
}
router.Run(":8080")
}
在上面的例子中,通过router.Group 创建了一个v1 路由前缀的路由组,我们设置路由规则时,不需要再设置路由前缀,框架会自动帮我们组装好。
同时,相同路由前缀的规则,也在同一个代码块里进行维护。 相比于 net/http 代码库,Gin 使得代码结构更清晰、更易于管理。
3. 中间件处理
在web应用请求处理过程中,除了执行具体的业务逻辑之外,往往需要在这之前执行一些通用的逻辑,比如鉴权操作,错误处理或者是日志打印功能,这些逻辑我们统称为中间件处理逻辑,而且往往是必不可少的。
首先对于错误处理,在应用程序的执行过程中,可能会发生一些内部错误,如数据库连接失败、文件读取错误等。合理的错误处理可以避免这些错误导致整个应用崩溃,而是通过适当的错误响应告知客户端。
对于鉴权操作,在许多web处理场景中,经常都是用户认证之后,才能访问某些受限资源或执行某些操作。同时鉴权操作还可以限制用户的权限,避免用户有未经授权的访问,这有助于提高程序的安全性。
因此,一个完整的HTTP请求处理逻辑,是极有可能需要这些中间件处理逻辑的。而且理论上框架或者类库应该有对中间件逻辑的支持。下面先来看看 net/http 能怎么去实现:
package main
import (
"fmt"
"log"
"net/http"
)
// 错误处理中间件
func errorHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
log.Printf("Panic: %v", err)
}
}()
next.ServeHTTP(w, r)
})
}
// 认证鉴权中间件
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 模拟身份验证
if r.Header.Get("Authorization") != "secret" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
// 处理业务逻辑
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
// 另外
func anotherHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Another endpoint")
}
func main() {
// 创建路由处理器
router := http.NewServeMux()
// 应用中间件, 注册处理器
handler := errorHandler(authMiddleware(http.HandlerFunc(helloHandler)))
router.Handle("/", handler)
// 应用中间件, 注册另外一个请求的处理器
another := errorHandler(authMiddleware(http.HandlerFunc(anotherHandler)))
router.Handle("/another", another)
// 启动服务器
http.ListenAndServe(":8080", router)
}
在上述示例中,我们在net/http 中通过errorHandler 和 authMiddleware 两个中间件实现了错误处理和鉴权功能。 接下来我们查看示例代码的第49行,可以发现代码通过装饰者模式,给原本的处理器增加了错误处理和鉴权操作功能。
这段代码的实现的优点,是通过装饰者模式,对多个处理函数进行组合,形成处理器链,实现了错误处理和认证鉴权功能。而不需要在每个处理函数handler 中去加上这部分逻辑,这使得代码具备更高的可读性和可维护性。
但是这里也存在着一个很明显的缺点,这个功能并不是框架给我们提供的,而是我们自己实现的。我们每新增一个处理函数handler, 都需要对这个handler 进行装饰,为其增加错误处理和鉴权操作,这在增加我们负担的同时,也容易出错。同时需求也是不断变化的,有可能部分请求只需要错误处理了,一部分请求只需要鉴权操作,一部分请求既需要错误处理也需要鉴权操作,基于这个代码结构,其会变得越来越难维护。
相比之下,Gin 框架提供了一种更灵活的方式来启用和禁用中间件逻辑,能针对某个路由组进行设置,而不需要对每个路由规则单独设置,下面展示下示例代码:
package main
import (
"github.com/gin-gonic/gin"
)
func authMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 模拟身份验证
if c.GetHeader("Authorization") != "secret" {
c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"})
return
}
c.Next()
}
}
func main() {
router := gin.Default()
// 全局添加 Logger 和 Recovery 中间件
// 创建一个路由组,该组中的所有路由都会应用 authMiddleware 中间件
authenticated := router.Group("/")
authenticated.Use(authMiddleware())
{
authenticated.GET("/hello", func(c *gin.Context) {
c.String(200, "Hello, World!")
})
authenticated.GET("/private", func(c *gin.Context) {
c.String(200, "Private data")
})
}
// 不在路由组中,因此没有应用 authMiddleware 中间件
router.GET("/welcome", func(c *gin.Context) {
c.String(200, "Welcome!")
})
router.Run(":8080")
}
在上述示例中,我们通过router.Group("/") 创建了一个名为 authenticated 的路由组,然后使用 Use 方法,给该路由组启用 authMiddleware 中间件。在这路由组下所有的路由规则,都会自动执行authMiddleware 实现的鉴权操作。
相对于net/http 的优点,首先是不需要对每个handler 进行装饰,增加中间件逻辑,用户只需要专注于业务逻辑的开发即可,减轻了负担。
其次可维护性更高了,如果业务需要不再需要进行鉴权操作,gin 只需要删除掉Use 方法的调用,而net/http 则需要对所有handler的装饰操作进行处理,删除掉装饰者节点中的鉴权操作节点,工作量相对于gin 来说非常大,同时也容易出错。
最后,gin 在处理不同部分的请求需要使用不同中间件的场景下,更为灵活,实现起来也更为简单。比如 一部分请求需要鉴权操作,一部分请求需要错误里处理,还有一部分既需要错误处理,也需要鉴权操作。这种场景下,只需要通过gin 创建三个路由组router, 然后不同的路由组分别调用 Use 方法启用不同的中间件,即可实现需求了,这相对于net/http 更为灵活和可维护。
这也是为什么有net/http 的前提下,还出现了gin 框架的重要原因之一。
4. 数据绑定
在处理HTTP请求时,比较常见的功能,是将请求中的数据自动绑定到结构体当中。下面以一个表单数据为例,如果使用net/http,如何将数据绑定到结构体当中:
package main
import (
"fmt"
"log"
"net/http"
)
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
func handleFormSubmit(w http.ResponseWriter, r *http.Request) {
var user User
// 将表单数据绑定到 User 结构体
user.Name = r.FormValue("name")
user.Email = r.FormValue("email")
// 处理用户数据
fmt.Fprintf(w, "用户已创建:%s (%s)", user.Name, user.Email)
}
func main() {
http.HandleFunc("/createUser", handleFormSubmit)
http.ListenAndServe(":8080", nil)
}
我们需要调用FormValue 方法,一个一个得从表单中读取出数据,然后设置到结构体当中。而且在字段比较多的情况下,我们很有可能漏掉其中的某些字段,导致后续处理逻辑出现问题。而且每个字段都需要我们手动读取设置,也很影响我们的开发效率。
下面我们来看看Gin 是如何读取表单数据,将其设置到结构体当中的:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
func handleFormSubmit(c *gin.Context) {
var user User
// 将表单数据绑定到 User 结构体
err := c.ShouldBind(&user)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的表单数据"})
return
}
// 处理用户数据
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("用户已创建:%s (%s)", user.Name, user.Email)})
}
func main() {
router := gin.Default()
router.POST("/createUser", handleFormSubmit)
router.Run(":8080")
}
看上面示例代码的第17行,可以看到直接调用ShouldBind 函数,便可以自动将表单的数据自动映射到结构体当中,不再需要一个一个字段读取,然后再单独设置到结构体当中。
相比于使用net/http, gin 框架在数据绑定方面更为方便,同时也不容易出错。gin 提供了各种 api , 能够将各种类型的数据映射到结构体当中,用户只需要调用对应的 api 即可。而net/http 则未提供相对应的操作,需要用户读取数据,然后手动设置到结构体当中。
5. 总结
在Go语言中, net/http 提供了基本的HTTP功能,但它的设计目标是简单和通用性,而不是提供高级特性和便利的开发体验。在处理HTTP请求和构建Web应用时,处理复杂的路由规则时,会显得力不从心;同时对于一些公共操作,比如日志记录,错误处理等,很难做到可插拔设计;想要将请求数据绑定到结构体中,net/http 也没有提供一些简易的操作,都是需要用户手动去实现的。
这就是为什么出现了像 Gin这样的第三方库,其是一个构建在 net/http 之上,旨在简化和加速Web应用程序的开发。
总的来说,Gin 可以帮助开发者更高效地构建Web应用程序,提供了更好的开发体验和更丰富的功能。当然,选择使用 net/http 还是 Gin 取决于项目的规模、需求和个人喜好。对于简单的小型项目,net/http 可能已经足够,但对于复杂的应用程序,Gin 可能会更适合。
有了net/http, 为什么还要有gin的更多相关文章
- go web framework gin 启动流程分析
最主要的package : gin 最主要的struct: Engine Engine 是整个framework的实例,它包含了muxer, middleware, configuration set ...
- Gin框架 - 自定义错误处理
目录 概述 错误处理 自定义错误处理 panic 和 recover 推荐阅读 概述 很多读者在后台向我要 Gin 框架实战系列的 Demo 源码,在这里再说明一下,源码我都更新到 GitHub 上, ...
- Gin框架04:趣谈参数绑定与校验
导读 在第二节,我们学习了Gin框架的路由定义与参数接收,今天应一位同学的要求,来讲解一下参数的绑定与校验. 为什么校验参数? 本不必抛出这个问题的,但顾及到初出茅庐的同学,这里解释一下. 假设做一个 ...
- 基于gin的golang web开发:访问mysql数据库
web开发基本都离不开访问数据库,在Gin中使用mysql数据库需要依赖mysql的驱动.直接使用驱动提供的API就要写很多样板代码.你可以找到很多扩展包这里介绍的是jmoiron/sqlx.另外还有 ...
- gin源码解读3-gin牛逼的context
Gin封装的最好的地方就是context和对response的处理. github的README的介绍,基本就是对这两个东西的解释. 本篇文章主要解释context的使用方法, 以及其设计原理 为什么 ...
- PGL图学习之图神经网络GraphSAGE、GIN图采样算法[系列七]
0. PGL图学习之图神经网络GraphSAGE.GIN图采样算法[系列七] 本项目链接:https://aistudio.baidu.com/aistudio/projectdetail/50619 ...
- 有了lisk,为什么我们还要做一个Asch?
0 前言 首先要声明一点,我们和我们的一些朋友都是lisk的投资人和支持者,我们也相信lisk会成功. 事实上,lisk已经成功了一半,目前在区块链领域融资金额排行第二,仅次于以太坊. 那为什么我们还 ...
- 连HTTPS都有漏洞,这么不安全的互联网我们还要继续用吗?
转载自 http://www.huxiu.com/article/45302/1.html 10月24日和25日,虎嗅君参加了GeekPwn(极棒)安全极客嘉年华活动. 嗯...说是嘉年华,其实就 ...
- 为什么重写equals方法还要重写hashcode方法?
我们都知道Java语言是完全面向对象的,在java中,所有的对象都是继承于Object类.Ojbect类中有两个方法equals.hashCode,这两个方法都是用来比较两个对象是否相等的. 在未重写 ...
- NoSQL专家王涛访谈:为什么我们还要做一个NoSQL?
ChinaUnix:各位网友大家好,今天有幸请到王涛先生到CU做客,与大家交流一些工作经验.首先请王涛先介绍一下自己. 王涛:大家好,我是王涛.过去八年里我一直在IBM多伦多实验室从事DB2引擎研发的 ...
随机推荐
- 【四】超级快速pytorch安装,三步走,分分钟完成!
相关文章: [一]tensorflow安装.常用python镜像源.tensorflow 深度学习强化学习教学 [二]tensorflow调试报错.tensorflow 深度学习强化学习教学 [三]t ...
- 8.1 Windows驱动开发:内核文件读写系列函数
在应用层下的文件操作只需要调用微软应用层下的API函数及C库标准函数即可,而如果在内核中读写文件则应用层的API显然是无法被使用的,内核层需要使用内核专有API,某些应用层下的API只需要增加Zw开头 ...
- 3.0 熟悉IDAPro静态反汇编器
IDA Pro 是一种功能强大且灵活的反汇编工具,可以在许多领域中发挥作用,例如漏洞研究.逆向工程.安全审计和软件开发等,被许多安全专家和软件开发者用于逆向工程和分析二进制代码.它支持大量的二进制文件 ...
- 21.10 Python 使用CRC32校验文件
CRC文件校验是一种用于验证文件完整性的方法,通过计算文件的CRC值并与预先计算的CRC校验值进行比较,来判断文件是否发生变化,此类功能可以用于验证一个目录中是否有文件发生变化,如果发生变化则我们可以 ...
- 我管你什么okr还是kpi,PPT轻松交给你
word一键转ppt 通过[文件]→[选项]→[快速访问工具栏],选择[不在功能区中的命令],找到[发送到Microsoft PowerPoint ],单击[添加]后再[确定] 调出功能按钮后,就可以 ...
- Gitee一个仓库存储多个项目
需求: 平时会做一些小项目,有时候一个小项目就几行代码,十几K的项目,给这些小项目建一个库保存太奢侈了太浪费了,所以换个思路,根据项目类型来创建库,然后每个小项目以孤立分支的方式存到该库中,这 ...
- 2、数据库:SQL Server部署 - 系统部署系列文章
对于微软的SQL Server的安装,以前已经有写过了,到了2022版本,安装没多大的改变,很多只需要少配置,然后直接下一步即可.现在是2023年了,SQL Server已经出到了2022版本,这篇博 ...
- Docker生命周期,一张图秒懂docker
- .NET Core开发实战(第6课:作用域与对象释放行为)--学习笔记(下)
06 | 作用域与对象释放行为 接下来,把服务切换为单例模式,通过工厂的方式 services.AddSingleton<IOrderService>(p => new Dispos ...
- 吉特日化MES & 日化制药工厂信息化系统架构图
作者:情缘 出处:http://www.cnblogs.com/qingyuan/ 关于作者:从事仓库,生产软件方面的开发,在项目管理以及企业经营方面寻求发展之路 版权声明:本文版权归作者和博客园 ...