Go语言学习 - RPC篇:gin框架的基础能力剖析

简介: gin是非常流行的一款HTTP框架。相较于原生的HTTP server,gin有很多改进点,主要在于3点:1. 上手简单,开发思路与原生HTTP基本一致2. 引入多个工具库,提高了开发效率3. 生态丰富,有许多开源的组件围绕着gin框架,我们将展开今天的话题。

gin框架

gin是非常流行的一款HTTP框架。相较于原生的HTTP server,gin有很多改进点,主要在于3点:

  1. 上手简单,开发思路与原生HTTP基本一致
  2. 引入多个工具库,提高了开发效率
  3. 生态丰富,有许多开源的组件

围绕着gin框架,我们将展开今天的话题。

// 请求结构体
type MyRequest struct {
    MyInfo string `form:"my_info" json:"my_info"`
}

// 响应结构体
type MyResponse struct {
    Errno  int    `json:"errno"`
    Result string `json:"result"`
    MyInfo string `form:"my_info" json:"my_info"`
}

// handler
func GetData(c *gin.Context) {
    var b MyRequest
    err := c.Bind(&b)
    if err != nil {
        c.JSON(http.StatusOK, MyResponse{
            Errno: 1,
        })
        return
    }

    c.JSON(http.StatusOK, MyResponse{
        Result: "my result",
    })
}

func main() {
    // gin server
    r := gin.Default()
    // 中间件
    r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
        if err, ok := recovered.(string); ok {
            c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err))
        }
        c.AbortWithStatus(http.StatusInternalServerError)
    }))

    r.GET("/data", GetData)
    r.Run()
}

关键函数分析

路由注册

func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes 

func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes

Gin支持不同HTTP方法的路由注册,这对RESTful风格的代码编写带来了很大帮助。对于阅读代码的同学,可以快速地通过路由注册的列表,如r.GET("/data", GetData),找到对应的方法。

Handler函数

type HandlerFunc func(*Context)

Handler函数相较于标准库,看似从2个参数RequestResponseWriter 转变成了一个参数 Context,简化了调用,但其实对调用者来说,复杂度并没有降低:

  1. Context 包含大量数据结构
  2. Context 包含了大量的方法

对于一名新手,在摸索出一条最佳实践路径前,学习成本不增反减。这主要是因为gin.Context过重。从编程角度来看,这个对象包含了过多信息,是个大而杂的工具集。

但不可否认的是,gin里提供了很多工具都比原生库好用,例如参数绑定、返回JSON数据。

绑定参数Bind

func (c *Context) Bind(obj any) error

Bind中引入了泛型中的any特性,但使用和原先的interface{}完全一致:

调用方可以填任意值。但实际上,Bind中必须为一个指针类型的数据结构,但由于interface{}对入参没有任何编译时的限制,导致传参问题在运行时才会报错。

例如:

var b MyRequest
// 正确
c.Bind(&b)
// 错误:编译正确,但运行时异常
c.Bind(b)
c.Bind(1)

返回JSON数据

func (c *Context) JSON(code int, obj any)

该方法是返回HTTP状态码为code,并且将obj数据进行JSON序列化。

它的问题同Bind函数,这里就不再赘述了。

middleware

gin框架提供了middleware的能力,它可以为整个Server提供一个公共能力的封装。有了middleware,整个server处理请求变成了:

middleware预处理 -> handler -> middleware后处理

  • 常见的预处理如

    • 参数校验
    • 用户认证
    • panic恢复
  • 常见的后处理则如

    • 定制HTTP状态码
    • 异常数据封装

总体来说,middleware能帮助用户减少重复性代码的编写,沉淀为公共能力,堪称web编程的一大利器。

gin能力剖析

我们先看看gin的改进点:

  1. mux支持RESTful风格的接口定义
  2. gin.Context提供了大量的工具,简化解析、返回的相关代码
  3. middleware可解决大量重复性的代码

这三点对开发者带来了不小的帮助。但是,我们在使用gin作为开发工具时,仍有一些问题:

  1. 大量的参数类型都是interface{}类型的数据结构,需要调用方自行保证
  2. gin.Context过大,学习和理解的成本很高
  3. 不少问题要在运行时才能发现,编译期无能为力

这些弊端汇总起来,依旧是和handler的函数定义相关:没有充分地利用Go强类型、编译检查的特点,来提高程序的质量、降低开发者的学习成本

更简单的Handler框架

那么,什么样的Handler框架对用户来说效果更好呢?我这边给出一个函数签名:

 func BetterHandler(ctx context.Context, req *MyRequest) (rsp *MyResponse, err error)

我们依次看一下这些参数及其使用场景:

  1. ctx - 上下文,传递公共参数以及超时控制
  2. req - 请求的参数结构
  3. rsp - 响应的参数结构
  4. err - 错误信息

从整个RPC框架来看,它重点做了2件事:

  1. 自动将http参数解析到ctx和req中

    1. 解析规则按标准约定,如HTTP RESTful
    2. 一般是将Header里的信息放到ctx中,将URL+Body里的信息匹配到req结构体
  2. 自动将rsp和err对应到HTTP响应中

    1. err=nil时,认为请求成功,将rsp序列化后、填入到HTTP Body中
    2. err!=nil时,认为请求去失败,返回约定的协议(如异常状态码、异常HTTP的Body)

BetterHandler是一个很棒的编程体验:

  1. 无需关心解析参数与返回响应这两步的具体实现,统一由框架封装
  2. 函数的输入和输出都是强类型的,开发者有了一个明确的“模板”
  3. 将handler中的业务逻辑与RPC框架中协议部分解耦

也许你一下子无法快速理解,但反复对比下,你会逐渐体会到其中的精妙。但是,使用这个框架前,我们要解决以下两个问题:

  1. URL与Handler的匹配逻辑
  2. 怎么约定解析请求和返回响应的协议

小结

今天,我们一起看了gin框架的相关示例,编程体验比原生http库有了明显提升。gin的生态也给出了不少的优化方案或者插件,但由于框架本身限制,很难治本。

下一讲,我们将来看一个我最为推荐的RPC框架,分析一下其相关利弊。

目录
相关文章
|
5天前
|
数据采集 存储 Go
使用Go语言和chromedp库下载Instagram图片:简易指南
Go语言爬虫示例使用chromedp库下载Instagram图片,关键步骤包括设置代理IP、创建带代理的浏览器上下文及执行任务,如导航至用户页面、截图并存储图片。代码中新增`analyzeAndStoreImage`函数对图片进行分析和分类后存储。注意Instagram的反爬策略可能需要代码适时调整。
使用Go语言和chromedp库下载Instagram图片:简易指南
|
1天前
|
安全 Go 开发者
Golang深入浅出之-Go语言并发编程面试:Goroutine简介与创建
【4月更文挑战第22天】Go语言的Goroutine是其并发模型的核心,是一种轻量级线程,能低成本创建和销毁,支持并发和并行执行。创建Goroutine使用`go`关键字,如`go sayHello("Alice")`。常见问题包括忘记使用`go`关键字、不正确处理通道同步和关闭、以及Goroutine泄漏。解决方法包括确保使用`go`启动函数、在发送完数据后关闭通道、设置Goroutine退出条件。理解并掌握这些能帮助开发者编写高效、安全的并发程序。
10 1
|
2天前
|
人工智能 Go 调度
掌握Go并发:Go语言并发编程深度解析
掌握Go并发:Go语言并发编程深度解析
|
2天前
|
SQL 关系型数据库 MySQL
Golang数据库编程详解 | 深入浅出Go语言原生数据库编程
Golang数据库编程详解 | 深入浅出Go语言原生数据库编程
|
2天前
|
Go 开发者
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
【4月更文挑战第21天】本文介绍了Go语言中的流程控制语句,包括`if`、`switch`和`for`循环。`if`语句支持简洁的语法和初始化语句,但需注意比较运算符的使用。`switch`语句提供多分支匹配,可省略`break`,同时支持不带表达式的形式。`for`循环有多种形式,如基本循环和`for-range`遍历,遍历时修改原集合可能导致未定义行为。理解并避免易错点能提高代码质量和稳定性。通过实践代码示例,可以更好地掌握Go语言的流程控制。
11 3
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
|
2天前
|
Go
Golang深入浅出之-Go语言函数基础:定义、调用与多返回值
【4月更文挑战第21天】Go语言函数是代码组织的基本单元,用于封装可重用逻辑。本文介绍了函数定义(包括基本形式、命名、参数列表和多返回值)、调用以及匿名函数与闭包。在函数定义时,注意参数命名和注释,避免参数顺序混淆。在调用时,要检查并处理多返回值中的错误。理解闭包原理,小心处理外部变量引用,以提升代码质量和可维护性。通过实践和示例,能更好地掌握Go语言函数。
17 1
Golang深入浅出之-Go语言函数基础:定义、调用与多返回值
|
3天前
|
程序员 Go API
【Go语言快速上手(二)】 分支与循环&函数讲解
【Go语言快速上手(二)】 分支与循环&函数讲解
|
4天前
|
Go
Golang深入浅出之-Go语言基础语法:变量声明与赋值
【4月更文挑战第20天】本文介绍了Go语言中变量声明与赋值的基础知识,包括使用`var`关键字和简短声明`:=`的方式,以及多变量声明与赋值。强调了变量作用域、遮蔽、初始化与零值的重要性,并提醒读者注意类型推断时的一致性。了解这些概念有助于避免常见错误,提高编程技能和面试表现。
19 0
|
4天前
|
编译器 Go 开发者
Go语言入门|包、关键字和标识符
Go语言入门|包、关键字和标识符
22 0
|
7天前
|
API Go
使用Go语言通过API获取代理IP并使用获取到的代理IP
使用Go语言通过API获取代理IP并使用获取到的代理IP