乐趣区

关于go: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 文档。

退出移动版