gin框架
gin是非常流行的一款HTTP框架。相较于原生的HTTP server,gin有很多改进点,主要在于3点:
- 上手简单,开发思路与原生HTTP基本一致
- 引入多个工具库,提高了开发效率
- 生态丰富,有许多开源的组件
围绕着gin框架,我们将展开今天的话题。
示例Gin代码
代码语言:javascript复制// 请求结构体
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()
}
关键函数分析
路由注册
代码语言:javascript复制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函数
代码语言:javascript复制type HandlerFunc func(*Context)
Handler函数相较于标准库,看似从2个参数Request
和 ResponseWriter
转变成了一个参数 Context
,简化了调用,但其实对调用者来说,复杂度并没有降低:
Context
包含大量数据结构Context
包含了大量的方法
对于一名新手,在摸索出一条最佳实践路径前,学习成本不增反减。这主要是因为gin.Context
过重。从编程角度来看,这个对象包含了过多信息,是个大而杂的工具集。
但不可否认的是,gin
里提供了很多工具都比原生库好用,例如参数绑定、返回JSON数据。
绑定参数Bind
代码语言:javascript复制func (c *Context) Bind(obj any) error
Bind中引入了泛型中的any
特性,但使用和原先的interface{}
完全一致:
调用方可以填任意值。但实际上,Bind中必须为一个指针类型的数据结构,但由于interface{}对入参没有任何编译时的限制,导致传参问题在运行时才会报错。
例如:
代码语言:javascript复制var b MyRequest
// 正确
c.Bind(&b)
// 错误:编译正确,但运行时异常
c.Bind(b)
c.Bind(1)
返回JSON数据
代码语言:javascript复制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的改进点:
- mux支持RESTful风格的接口定义
gin.Context
提供了大量的工具,简化解析、返回的相关代码- middleware可解决大量重复性的代码
这三点对开发者带来了不小的帮助。但是,我们在使用gin
作为开发工具时,仍有一些问题:
- 大量的参数类型都是
interface{}
类型的数据结构,需要调用方自行保证 gin.Context
过大,学习和理解的成本很高- 不少问题要在运行时才能发现,编译期无能为力
这些弊端汇总起来,依旧是和handler的函数定义相关:没有充分地利用Go强类型、编译检查的特点,来提高程序的质量、降低开发者的学习成本。
更简单的Handler框架
那么,什么样的Handler框架对用户来说效果更好呢?我这边给出一个函数签名:
代码语言:javascript复制 func BetterHandler(ctx context.Context, req *MyRequest) (rsp *MyResponse, err error)
我们依次看一下这些参数及其使用场景:
- ctx - 上下文,传递公共参数以及超时控制
- req - 请求的参数结构
- rsp - 响应的参数结构
- err - 错误信息
从整个RPC框架来看,它重点做了2件事:
- 自动将http参数解析到ctx和req中
- 解析规则按标准约定,如HTTP RESTful
- 一般是将Header里的信息放到ctx中,将URL Body里的信息匹配到req结构体
- 自动将rsp和err对应到HTTP响应中
- err=nil时,认为请求成功,将rsp序列化后、填入到HTTP Body中
- err!=nil时,认为请求去失败,返回约定的协议(如异常状态码、异常HTTP的Body)
BetterHandler
是一个很棒的编程体验:
- 无需关心解析参数与返回响应这两步的具体实现,统一由框架封装
- 函数的输入和输出都是强类型的,开发者有了一个明确的“模板”
- 将handler中的业务逻辑与RPC框架中协议部分解耦
也许你一下子无法快速理解,但反复对比下,你会逐渐体会到其中的精妙。但是,使用这个框架前,我们要解决以下两个问题:
- URL与Handler的匹配逻辑
- 怎么约定解析请求和返回响应的协议
小结
今天,我们一起看了gin框架的相关示例,编程体验比原生http库有了明显提升。gin的生态也给出了不少的优化方案或者插件,但由于框架本身限制,很难治本。
下一讲,我们将来看一个我最为推荐的RPC框架,分析一下其相关利弊。
Github: https://github.com/Junedayday/code_reading Blog: http://junes.tech/ Bilibili: https://space.bilibili.com/293775192