写点什么

「Go 框架」深入理解 gin 框中 Context 的 Request 和 Writer 对象

作者:Go学堂
  • 2023-04-23
    北京
  • 本文字数:1980 字

    阅读完需:约 6 分钟

「Go框架」深入理解gin框中Context的Request和Writer对象

大家好,我是渔夫子。


今天跟大家聊一聊 gin 中 Context 对象中的 Request 和 Writer 对象。

背景

在使用 gin 框架时,我们定义的请求处理器,输入参数总是一个 gin.Context 的指针类型,代表请求的上下文。在处理器的业务逻辑中,通过 Context.Request 可以获取本次请求的参数值;通过 Context.Writer 就能将响应结果输出给客户端了。如下代码所示:


package main
import ( "github.com/gin-gonic/gin" "log" "net/http")
func main() { r := gin.Default()
// 注册路由,定义请求处理器函数 r.GET("/", func(c *gin.Context) { c.Param c.Writer.Write([]byte("Hello World"))
})
// 调用该函数,则禁用日志带颜色输出 gin.DisableConsoleColor() //使用该函数,则强制日志带颜色输出,无论是在终端还是其他输出设备 gin.ForceConsoleColor()
r.Run("127.0.0.1:8080")}
复制代码


那么,Context 字段是在什么地方初始化的呢,为什么通过 Context.Request 字段就能读取请求的参数呢,又为什么通过 Context.Writer 字段就能将响应结果输出给客户端呢?接下来我们就一一解答这 3 个问题。

Context 对象的初始化

gin 的 Context 对象实际上是在 Engine 对象的 ServeHTTP 函数中进行初始化的,如下:


// ServeHTTP conforms to the http.Handler interface.func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {  c := engine.pool.Get().(*Context)  c.writermem.reset(w)  c.Request = req  c.reset()
engine.handleHTTPRequest(c)
engine.pool.Put(c)}
复制代码


通过该函数我们可以看到,在第 3 行通过池化技术获取到一个新的Context对象。获取到该Context后,通过 engine.handleHTTPRequest 函数传入 Context 参数,进而找到对应的路由处理器,传递给具体的路由处理器。在上例中就是 r.GET 中的对应处理器。


在这个函数中,还会把本次的请求体req赋值给Context.Request变量。这样,在具体的请求处理器中就可以通过Context的各种方法从Request中获取参数值了。比如GetHeaderGetRawData等。


我们再来回顾下 go 的 http 的启动和请求处理流程,以便了解 ServeHTTP 的调用时机。engine 对象实际上是实现了 go 的标准包中的 Handler 接口。该接口中定义了 ServeHTTP 方法。在接收到请求后,net/http 的包就会调用 engine 的 ServeHTTP 方法。如下图中的 engine.ServeHTTP 部分:


Context.Request 对象

在上节中,在 Engine 的 ServeHTTP 函数中,将函数的入参 req 赋值给了 Context 的 Request 对象。那么,这个 req 变量对象是从哪里来的呢?


Engine.ServeHTTP函数是在net/http/server.go文件的conn.serve函数中被调用的。conn代表本次请求的连接对象。conn.serve函数首先会从 conn 对象中读取出本次的请求参数(包含请求头等),并解析到request对象中,将该对象再赋值给 Context 中的 Request 字段。这样,通过 Context 的 Param、Form、Header 等函数,就可以从 Request 中读取信息了。


Context.Writer 对象

在具体的请求处理函数中,我们发现所有的输出都是通过Context.Writer对象的Write函数将数据返回给客户端的。那么,这里的Context.Writer对象是什么呢,为什么通过Context.Writer就是能结果返回给客户端呢?


要回答这个问题,我们还需要回到net/http/server.go文件的conn.serve函数中,response参数是在该函数中调用ServeHTTP时传入的参数,而该response参数是通过conn.readRequest函数返回的。



我们再通过 readRequest 函数来看下 response 结构体的关键字段,如下:



我们再来看 Engine.ServeHTTP 函数:首先,将 response 对象赋值给 Context.writermem 对象。即在 c.writermem.reset(w)函数中执行的。其次,Context.Writer 是对 Context.writermem 的引用。即 Context.Writer 依然是指向 response 对象。即在 c.reset()函数中执行的。



所以,在 Context 中跟 response 有关的关键字段如下:



最后,在业务逻辑中使用 Context.Writer.Write 函数输出时,实际上是执行的 response.Write 函数。


我们再来看下 response.Write 函数的实现:



写入的时候是调用的responsew字段写入的。那 w 字段又是什么呢?我们再回到server.go中的对response初始化的readRequest函数中,如下:



可以看到wresponse的对象。同时将 w 还赋值给了w.cw.res。最后,w.w字段是基于w.cw的一个bufio.Writer对象。当调用bufio.WriterFlush时,实际上是调用了bufio.Writer中的wrWrite方法。而wr的又是指向了chunkWriter对象的Write方法。最后该方法是执行了w.conn.bufw.Write写入了数据。


最终的写入流程如下:


总结

本文深入分析了 gin 框架中 Context 中读取请求中的数据以及写入响应数据的原理。本质上 Request 的数据来源于 conn 对象。最终也是写入到 conn 对象的 bufw 中。


---特别推荐---


特别推荐:一个专注 go 项目实战、项目中踩坑经验及避坑指南、各种好玩的 go 工具的公众号,「Go 学堂」,专注实用性,非常值得大家关注。关注送《100 个 go 常见的错误》pdf 文档。

发布于: 刚刚阅读数: 3
用户头像

Go学堂

关注

微信公众号:Go学堂 2019-08-06 加入

专注Go编程知识、案例、常见错误及原理分析。意在通过阅读更多优秀的代码,提高编程技能。同名公众号「Go学堂」期待你的关注

评论

发布
暂无评论
「Go框架」深入理解gin框中Context的Request和Writer对象_golang_Go学堂_InfoQ写作社区