1. 简介

在Go语言中,net/http 包提供了一个强大且灵活的标准HTTP库,可以用来构建Web应用程序和处理HTTP请求。这个包是Go语言标准库的一部分,因此所有的Go程序都可以直接使用它。既然已经有 net/http 这样强大和灵活的标准库,为什么还出现了像 Gin 这样的,方便我们构建Web应用程序的第三方库?

其实在于net/http的定位,其提供了基本的HTTP功能,但它的设计目标是简单和通用性,而不是提供高级特性和便利的开发体验。在处理HTTP请求和构建Web应用时,可能会遇到一系列的问题,这也造就了Gin 这样的第三方库的出现。

下文我们将对一系列场景的介绍,通过比对 net/httpGin 二者在这些场景下的不同实现,进而说明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 中通过errorHandlerauthMiddleware 两个中间件实现了错误处理和鉴权功能。 接下来我们查看示例代码的第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的更多相关文章

  1. go web framework gin 启动流程分析

    最主要的package : gin 最主要的struct: Engine Engine 是整个framework的实例,它包含了muxer, middleware, configuration set ...

  2. Gin框架 - 自定义错误处理

    目录 概述 错误处理 自定义错误处理 panic 和 recover 推荐阅读 概述 很多读者在后台向我要 Gin 框架实战系列的 Demo 源码,在这里再说明一下,源码我都更新到 GitHub 上, ...

  3. Gin框架04:趣谈参数绑定与校验

    导读 在第二节,我们学习了Gin框架的路由定义与参数接收,今天应一位同学的要求,来讲解一下参数的绑定与校验. 为什么校验参数? 本不必抛出这个问题的,但顾及到初出茅庐的同学,这里解释一下. 假设做一个 ...

  4. 基于gin的golang web开发:访问mysql数据库

    web开发基本都离不开访问数据库,在Gin中使用mysql数据库需要依赖mysql的驱动.直接使用驱动提供的API就要写很多样板代码.你可以找到很多扩展包这里介绍的是jmoiron/sqlx.另外还有 ...

  5. gin源码解读3-gin牛逼的context

    Gin封装的最好的地方就是context和对response的处理. github的README的介绍,基本就是对这两个东西的解释. 本篇文章主要解释context的使用方法, 以及其设计原理 为什么 ...

  6. PGL图学习之图神经网络GraphSAGE、GIN图采样算法[系列七]

    0. PGL图学习之图神经网络GraphSAGE.GIN图采样算法[系列七] 本项目链接:https://aistudio.baidu.com/aistudio/projectdetail/50619 ...

  7. 有了lisk,为什么我们还要做一个Asch?

    0 前言 首先要声明一点,我们和我们的一些朋友都是lisk的投资人和支持者,我们也相信lisk会成功. 事实上,lisk已经成功了一半,目前在区块链领域融资金额排行第二,仅次于以太坊. 那为什么我们还 ...

  8. 连HTTPS都有漏洞,这么不安全的互联网我们还要继续用吗?

    转载自 http://www.huxiu.com/article/45302/1.html 10月24日和25日,虎嗅君参加了GeekPwn(极棒)安全极客嘉年华活动.   嗯...说是嘉年华,其实就 ...

  9. 为什么重写equals方法还要重写hashcode方法?

    我们都知道Java语言是完全面向对象的,在java中,所有的对象都是继承于Object类.Ojbect类中有两个方法equals.hashCode,这两个方法都是用来比较两个对象是否相等的. 在未重写 ...

  10. NoSQL专家王涛访谈:为什么我们还要做一个NoSQL?

    ChinaUnix:各位网友大家好,今天有幸请到王涛先生到CU做客,与大家交流一些工作经验.首先请王涛先介绍一下自己. 王涛:大家好,我是王涛.过去八年里我一直在IBM多伦多实验室从事DB2引擎研发的 ...

随机推荐

  1. TienChin-课程管理-配置课程字典

    课程类型 课程适用人群

  2. TienChin 渠道管理-渠道搜索

    ChannelController @PreAuthorize("hasPermission('tienchin:channel:list')") @GetMapping(&quo ...

  3. 新来的一个同事,把SpringBoot参数校验玩的那叫一个优雅

    介绍 在开发现代应用程序时,数据验证是确保用户输入的正确性和应用程序数据完整性的关键方面.Spring Boot 提供了强大的数据验证机制,使开发者能够轻松地执行验证操作.本文将深入介绍 Spring ...

  4. Flask Paginate实现表格分页

    flask_paginate 是 Flask 框架的一个分页扩展,用于处理分页相关的功能.它可以帮助你在 Flask Web 应用程序中实现分页功能,让用户可以浏览大量数据的不同部分.本篇博文重点讲述 ...

  5. LyScript 自实现汇编搜索功能

    通过对LyScript自动化插件进行二次封装,实现从内存中读入目标进程解码后的机器码,并通过Python代码在这些机器码中寻找特定的十六进制字符数组,或直接检索是否存在连续的反汇编指令片段等功能. 插 ...

  6. 面试官:什么是JIT、逃逸分析、锁消除、栈上分配和标量替换?

    JIT.逃逸分析.锁消除.栈上分配和标量替换等都属于 JVM 的优化手段,JVM 优化手段是指在运行 Java 程序时,通过对字节码的编译和执行过程进行优化,以提升程序的性能和效率. JVM 优化手段 ...

  7. 2024-01-31:用go语言,机器人正在玩一个古老的基于DOS的游戏, 游戏中有N+1座建筑,从0到N编号,从左到右排列, 编号为0的建筑高度为0个单位,编号为i的建筑的高度为H(i)个单位, 起

    2024-01-31:用go语言,机器人正在玩一个古老的基于DOS的游戏, 游戏中有N+1座建筑,从0到N编号,从左到右排列, 编号为0的建筑高度为0个单位,编号为i的建筑的高度为H(i)个单位, 起 ...

  8. [奶奶看了都会]ChatGPT接入企业微信成为聊天机器人

    1.聊天效果 上次给大家讲了ChatGPT接入个人微信的方法,但是个人微信容易被封号.这次就教大家接入企业微信,不会再被封号哦~ 话不多说,直接看机器人的聊天效果.基本能实现ChatGPT的聊天效果了 ...

  9. 使用7-zip进行分卷压缩和解分卷压缩(Windows和Linux)

    现在一共有10个视频,一共313M,我对该文件夹进行分卷压缩,每个tar包100M,压缩过程如下: Windows环境首先选中所有的压缩包,然后在压缩包上单击鼠标右键,然后选择7-Zip,再选择提取到 ...

  10. NC16122 郊区春游

    题目链接 题目 题目描述 今天春天铁子的班上组织了一场春游,在铁子的城市里有n个郊区和m条无向道路,第i条道路连接郊区Ai和Bi,路费是Ci.经过铁子和顺溜的提议,他们决定去其中的R个郊区玩耍(不考虑 ...