当前位置: 首页 > news >正文

Gin 优雅打印请求与回包内容

文章目录

  • 1.Gin 的 Middleware
  • 2.使用 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 内容,并多次使用- 掘金

http://www.lryc.cn/news/28500.html

相关文章:

  • 关于k8s中ETCD集群备份灾难恢复的一些笔记
  • 【设计模式之美 设计原则与思想:设计原则】19 | 理论五:控制反转、依赖反转、依赖注入,这三者有何区别和联系?
  • 2023年全国最新高校辅导员精选真题及答案13
  • 【XXL-JOB】XXL-JOB定时处理视频转码
  • optuna用于pytorch的轻量级调参场景和grid search的自定义设计
  • 语法篇--汇编语言先导浅尝
  • 【ID:17】【20分】A. DS顺序表--类实现
  • 【java web篇】Tomcat的基本使用
  • MySQL实战解析底层---行锁功过:怎么减少行锁对性能的影响
  • 初识STM32单片机
  • 数据结构与算法系列之单链表
  • MySQL基础
  • 面试热点题:环形链表及环形链表寻找环入口结点问题
  • 【算法】DFS与BFS
  • 湖州银行冲刺A股上市:计划募资约24亿元,资产质量水平较高
  • 高性能网络I/O框架-netmap源码分析
  • SpringBoot监听机制-以及使用
  • 若依学习——定时任务代码逻辑 详细梳理(springboot整合Quartz)
  • C++---最长上升子序列模型---拦截导弹(每日一道算法2023.3.4)
  • 【机器学习面试】百面机器学习笔记和问题总结+扩展面试题
  • 【2021.12.28】ctf逆向中的迷宫问题(含exe及wp)
  • WSL2使用Nvidia-Docker实现深度学习环境自由部署
  • SpringBoot入门 - 配置热部署devtools工具
  • CANFDNET-200U-UDP配置与数据收发控制
  • 嵌入式中backtrace的使用
  • CV学习笔记-Faster-RCNN
  • 大型三甲医院云HIS系统源码 强大的电子病历+完整文档
  • 如何使用Spring Cloud搭建高可用的Elasticsearch集群?详解Elasticsearch的安装与配置及Spring Boot集成的实现
  • phpinfo包含临时文件Getshell全过程及源码
  • ubuntu22.04 Desktop 服务器安装