建设银行官方网站登录入口,重庆快速排名,网页与网站的区别与联系是什么,从化五屏网站建设文章目录1.Gin 的 Middleware2.使用 Middleware 打印请求与回包内容3.多次读取请求 Body 的问题4.多次读取响应 Body 的问题5.小结参考文献在开发 Web 应用程序时#xff0c;难免不会遇到功能或性能等问题。为了快速定位问题#xff0c;需要打印请求和响应的内容。本文将介绍…
文章目录1.Gin 的 Middleware2.使用 Middleware 打印请求与回包内容3.多次读取请求 Body 的问题4.多次读取响应 Body 的问题5.小结参考文献在开发 Web 应用程序时难免不会遇到功能或性能等问题。为了快速定位问题需要打印请求和响应的内容。本文将介绍如何使用 Gin 框架来优雅地打印请求和响应的内容。
1.Gin 的 Middleware
在 Gin 框架中中间件是一种用于拦截 HTTP 请求和响应的机制。中间件函数可以在请求到达处理程序之前或之后执行某些操作例如打印请求和响应的内容、验证请求数据等。
Gin 框架提供了一种简单的方法来定义和使用中间件。中间件函数需要满足以下条件
函数的签名必须是 func(c *gin.Context)其中 c 是 Gin 框架中的上下文对象。函数可以执行任何操作但是必须调用 c.Next() 方法来继续执行请求处理程序和其他中间件函数。如果需要在请求处理程序之后执行某些操作可以在调用 c.Next() 之后执行。
2.使用 Middleware 打印请求与回包内容
下面是一个使用 Gin 中间件来打印请求和响应内容的示例代码
func Logger() 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查看请求和回包的内容。
3.多次读取请求 Body 的问题
实际上上面的做法会有问题。
在中间件中读取了请求的 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 方法中什么也不做。这样就可以在读取数据后不关闭相关资源从而方便进行其他操作或者多次读取同一个资源。
4.多次读取响应 Body 的问题
同样地在中间件中读取响应 Body 的问题是它会使得缓冲区被读取完毕指针指向了缓冲区的末尾而后续的代码再次读取 Body 时指针已经到了缓冲区的末尾无法再次读取。
为了避免这个问题我们可以使用一个自定义的 ResponseWriter 来替换 Gin 默认的 ResponseWriter。自定义的 ResponseWriter 可以将响应 Body 写入到一个内存缓冲区中并在中间件中获取响应 Body 并记录日志。
下面是一个完整的可以在日志中间件中读取请求与响应 Body 的示例。
// CustomResponseWriter 封装 gin ResponseWriter 用于获取回包内容。
type CustomResponseWriter struct {gin.ResponseWriterbody *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()// 使用自定义 ResponseWritercrw : 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)}
}5.小结
在本文中我们介绍了为什么要打印请求与回包内容以及如何使用 Gin 的 Middleware 功能来打印请求和回包内容。通过打印请求和回包内容我们可以更好地了解 API 的执行过程并且可以快速定位问题。 参考文献
OpenAI ChatGPT Using middleware | Gin Web Framework 如何让gin正确多次读取http request body内容- 掘金 如何让gin 正确读取http response body 内容并多次使用- 掘金