本文将介绍如何使用 Gin 框架来优雅地打印请求和响应的内容。
Gin 是一种轻量级的 Web 框架,用于构建高性能的 Web 应用程序。它具有快速、简单和易于使用的特点,并且具有许多可扩展的功能,如中间件。
在 Gin 框架中,中间件是一种用于拦截 HTTP 请求和响应的机制。中间件函数可以在请求到达处理函数之前或之后执行某些操作,例如:
Gin 框架提供了一种简单的方法来定义和使用中间件。中间件函数需要满足以下条件:
下面是一个使用 Gin 中间件来打印请求和响应内容的示例代码:
func LoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 记录请求时间
start := time.Now()
// 打印请求信息
reqBody, _ := c.GetRawData()
fmt.Printf("[INFO] Request: %s %s %s\n", c.Request.Method, c.Request.RequestURI, reqBody)
// 执行请求处理程序和其他中间件函数
c.Next()
// 记录回包内容和处理时间
end := time.Now()
latency := end.Sub(start)
respBody := string(c.Writer.Body.Bytes())
fmt.Printf("[INFO] Response: %s %s %s (%v)\n", c.Request.Method, c.Request.RequestURI, respBody, latency)
}
}
func main() {
r := gin.Default()
// 注册中间件
r.Use(Logger())
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello, World!")
})
r.Run(":8080")
}
上面的代码定义了一个中间件,用来记录请求和回包内容。在中间件中,我们首先记录请求的时间和请求内容,然后调用 c.Next() 继续处理请求。在请求处理完成后,我们记录回包内容和处理时间。最后,我们使用 gin.Default() 函数来创建一个 Gin 引擎实例,并注册路由和中间件。启动服务后,我们可以访问http://localhost:8080/hello
查看请求和回包的内容。
实际上,上面的做法会有问题。
在中间件中读取了请求的 Body,如果在接口处理函数中再次读取 Body,会导致 Body 被读取两次,从而出现问题。 因为在读取 Body 后,Body 的指针会被移到末尾,第二次读取时就无法再次读取到内容。
那么 Gin 如何正确多次读取 http request body 的内容呢?
解决思路: 由于 Request.Body 为公共变量,我们在对原有的 buffer 读取完成后,只要手动创建一个新的 buffer 然后以同样接口形式替换掉原有的 Request.Body 即可。
reqBytes, _ := c.GetRawData()
// 请求包体写回。
if len(reqBytes) > 0 {
c.Request.Body = io.NopCloser(bytes.NewBuffer(reqBytes))
}
在 Go 语言中,io.NopCloser 函数返回一个实现 io.ReadCloser 接口的对象,这个对象可以包装任何实现 io.Reader 接口的对象,并提供了一个空的 Close 方法。这个方法的作用就是什么也不做,仅仅是返回一个 nil 的 error。
通常情况下,我们会使用 io.ReadCloser 接口读取数据,并在读取完成后关闭相关资源,例如打开的文件句柄或者网络连接等。但是在某些场景下,我们希望读取数据,但是并不想关闭相关资源,比如在数据读取完成后还需要进行一些其他操作,或者需要多次读取同一个资源等。
这时候,就可以使用 io.NopCloser 函数将一个实现了 io.Reader 接口的对象包装成一个实现了 io.ReadCloser 接口的对象,并在 Close 方法中什么也不做。这样就可以在读取数据后不关闭相关资源,从而方便进行其他操作或者多次读取同一个资源。
同样地,在中间件中读取响应 Body 的问题是,它会使得缓冲区被读取完毕,指针指向了缓冲区的末尾,而后续的代码再次读取 Body 时,指针已经到了缓冲区的末尾,无法再次读取。
为了避免这个问题,我们可以使用一个自定义的 ResponseWriter 来替换 Gin 默认的 ResponseWriter。自定义的 ResponseWriter 可以将响应 Body 写入到一个内存缓冲区中,并在中间件中获取响应 Body 并记录日志。
下面是一个完整的可以在日志中间件中读取请求与响应 Body 的示例。
// CustomResponseWriter 封装 gin ResponseWriter 用于获取回包内容。
type CustomResponseWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (w CustomResponseWriter) Write(b []byte) (int, error) {
w.body.Write(b)
return w.ResponseWriter.Write(b)
}
// 日志中间件。
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
// 记录请求时间
start := time.Now()
// 使用自定义 ResponseWriter
crw := &CustomResponseWriter{
body: bytes.NewBufferString(""),
ResponseWriter: c.Writer,
}
c.Writer = crw
// 打印请求信息
reqBody, _ := c.GetRawData()
fmt.Printf("[INFO] Request: %s %s %s\n", c.Request.Method, c.Request.RequestURI, reqBody)
// 执行请求处理程序和其他中间件函数
c.Next()
// 记录回包内容和处理时间
end := time.Now()
latency := end.Sub(start)
respBody := string(crw.body.Bytes())
fmt.Printf("[INFO] Response: %s %s %s (%v)\n", c.Request.Method, c.Request.RequestURI, respBody, latency)
}
}
在本文中,我们介绍了为什么要打印请求与回包内容,以及如何使用 Gin 的 Middleware 功能来打印请求和回包内容。通过打印请求和回包内容,我们可以更好地了解 API 的执行过程,并且可以快速定位问题。
OpenAI ChatGPT
Using middleware | Gin Web Framework
如何让gin正确多次读取http request body内容- 掘金
如何让gin 正确读取http response body 内容,并多次使用- 掘金