场景
假设我们有一个GET接口/data,它返回一大堆数据,它运行的非常好
不知道为什么,项目经理突然发癫,让你把这个接口的响应数据输出到某个地方
(我才不会说是因为我想不出场景)
如果只修改这一个接口还好,在原来的接口加一个日志输出即可
就当你哼着小曲准备改代码的时候,项目经理再次发癫,要你把所有的响应数据都输出到日志
你无语了,如果还按照上面的方法,那就是每个接口都要加一个日志输出
如果有这么一个方法,可以在中间件里处理响应,那该多好啊
于是便有了下面的内容
方案
在使用gin框架开发web的时候,有时会需要对接口返回的数据进行处理,但是gin框架并没有提供相关的方法
我们可以手动拓展,来实现这一功能
代码
整体的逻辑大概如下
r.Use(func(c *gin.Context) {
// 一些中间件操作
lulululalala()
// 让处理函数执行
c.Next()
// 处理响应
log.Println(c.Writer.body)
})
我们需要定义一个新的Writer,来取代原先的Writer
type MyResponseWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
这里的gin.ResponseWriter就是gin.Context原先的Writer,通过组合它,可以让它在gin框架下随意驰骋
body是一个字节池,可以用于存放字节数据
光有结构还不够,我们还需要实现一个方法
func (rw *MyResponseWriter) Write(data []byte) (int, error) {
rw.body.Write(data)
return rw.ResponseWriter.Write(data)
}
通过改写Write方法,让我们可以把响应写到我们自定义的body中
然后,我们在中间件里,把gin.Context改造一下
r.Use(func(c *gin.Context) {
myWriter := &MyResponseWriter{
ResponseWriter: c.Writer,
body: bytes.NewBufferString(""),
}
c.Writer = myWriter // 替换默认的 ResponseWriter
c.Next()
})
如此一来,这个api在调用Writer时,用的就是我们定义的Writer了
完整示例
package main
import (
"bytes"
"log"
"net/http"
"github.com/gin-gonic/gin"
)
type MyResponseWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (rw *MyResponseWriter) Write(data []byte) (int, error) {
rw.body.Write(data)
return rw.ResponseWriter.Write(data)
}
func main() {
r := gin.Default()
r.Use(func(c *gin.Context) {
myWriter := &MyResponseWriter{
ResponseWriter: c.Writer,
body: bytes.NewBufferString(""),
}
c.Writer = myWriter
c.Next()
log.Println(myWriter.body.String())
})
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": nil,
"msg": "pong",
})
})
r.Run(":8080")
}
运行后,请求/ping接口
krean@Kreans-Air test % curl localhost:8080/ping
{"code":200,"data":null,"msg":"pong"}%
在终端应该能看到响应输出
2024/11/27 21:48:29 {"code":200,"data":null,"msg":"pong"}
[GIN] 2024/11/27 - 21:48:29 | 200 | 259.542µs | ::1 | GET "/ping"
结语
实际上,这类方法用的是不多的,也是为什么gin框架(以及大部分框架不封装这部分功能),总有更好的方法来处理这类问题
所以,这算是作为应对特别情况的紧急方案吧
Author:KreanXie