大家好,我是渔夫子。
明天跟大家聊一聊gin中Context对象中的Request和Writer对象。
背景
在应用gin框架时,咱们定义的申请处理器,输出参数总是一个gin.Context的指针类型,代表申请的上下文。在处理器的业务逻辑中,通过Context.Request能够获取本次申请的参数值;通过Context.Writer就能将响应后果输入给客户端了。如下代码所示:
package mainimport ( "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
中获取参数值了。比方GetHeader
、GetRawData
等。
咱们再来回顾下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函数的实现:
写入的时候是调用的response
的w
字段写入的。那w字段又是什么呢?咱们再回到server.go
中的对response
初始化的readRequest
函数中,如下:
能够看到w
是response
的对象。同时将w还赋值给了w.cw.res
。最初,w.w
字段是基于w.cw
的一个bufio.Writer
对象。当调用bufio.Writer
的Flush
时,实际上是调用了bufio.Writer
中的wr
的Write
办法。而wr
的又是指向了chunkWriter
对象的Write
办法。最初该办法是执行了w.conn.bufw.Write
写入了数据。
最终的写入流程如下:
总结
本文深入分析了gin框架中Context中读取申请中的数据以及写入响应数据的原理。实质上Request的数据来源于conn对象。最终也是写入到conn对象的bufw中。
特地举荐:一个专一go我的项目实战、我的项目中踩坑教训及避坑指南、各种好玩的go工具的公众号,「Go学堂」,专一实用性,十分值得大家关注。点击下方公众号卡片,间接关注。关注送《100个go常见的谬误》pdf文档。