Gin 框架在 middleware 中获取 response body 的方法

在写一个Gin框架日志中间件的时候,需要记录请求和响应相关的一些数据,例如请求参数、请求方法、请求时间、请求头、耗时、响应状态码、响应数据等,gin为获取这些数据基本都提供了现成的方法,但是获取响应数据还是有一定难度和复杂度的。

那么,该如何获取响应数据也就是 response body 呢?先上代码。

代码示例

1、先写一个 middleware ,简单打印一下 response body

package middleware

import (
   "bytes"
   "fmt"
   "github.com/gin-gonic/gin"
)

//自定义一个结构体,实现 gin.ResponseWriter interface
type responseWriter struct {
   gin.ResponseWriter
   b *bytes.Buffer
}

//重写 Write([]byte) (int, error) 方法
func (w responseWriter) Write(b []byte) (int, error) {
   //向一个bytes.buffer中写一份数据来为获取body使用
   w.b.Write(b)
   //完成gin.Context.Writer.Write()原有功能
   return w.ResponseWriter.Write(b)
}

func PrintResponse(c *gin.Context) {
   writer := responseWriter{
      c.Writer,
      bytes.NewBuffer([]byte{}),
   }
   c.Writer = writer

   c.Next()

   fmt.Println("response body:" + writer.b.String())
}

2、引用 middleware ,看看效果

package main

import (
   "github.com/gin-gonic/gin"
   "hello/middleware"
   "net/http"
)

func main() {
   r := gin.New()

   //添加获取响应内容 middleware
   r.Use(middleware.PrintResponse)

   r.GET("/test", func(c *gin.Context) {
      c.JSON(http.StatusOK, gin.H{"name": "xiaoming"})
   })
   // Listen and Server in 0.0.0.0:8080
   r.Run(":8080")
}

打开浏览器访问 http://127.0.0.1:8080/test ,即可在控制台中看到 response body 的输出内容

response body:{"name":"xiaoming"}

原理

通过上面的代码可以看出,gin 通过调用如下方法写入了 response body

 c.JSON(http.StatusOK, gin.H{"name": "xiaoming"})

追进 JSON 方法,源码如下,调用了 Render 方法

package gin

func (c *Context) JSON(code int, obj interface{}) {
   c.Render(code, render.JSON{Data: obj})
}

追进 Render 方法, 可以看出 gin.Context.Writer 作为参数传给了 r.Render() 方法,这里形参 r 的实参为 render.JSON{Data: obj} ,所以实际调用的是 func (r JSON) Render(w http.ResponseWriter) 方法。

package gin

// Render writes the response headers and calls render.Render to render data.
func (c *Context) Render(code int, r render.Render) {
   c.Status(code)

   if !bodyAllowedForStatus(code) {
      r.WriteContentType(c.Writer)
      c.Writer.WriteHeaderNow()
      return
   }

   if err := r.Render(c.Writer); err != nil {
      panic(err)
   }
}

func (r JSON) Render(w http.ResponseWriter) 源码如下

package render

// Render (JSON) writes data with custom ContentType.
func (r JSON) Render(w http.ResponseWriter) (err error) {
   if err = WriteJSON(w, r.Data); err != nil {
      panic(err)
   }
   return
}

// WriteJSON marshals the given interface object and writes it with custom ContentType.
func WriteJSON(w http.ResponseWriter, obj interface{}) error {
   writeContentType(w, jsonContentType)
   jsonBytes, err := json.Marshal(obj)
   if err != nil {
      return err
   }
   _, err = w.Write(jsonBytes)
   return err
}

在 func (r JSON) Render() 方法中,gin.Context.Writer 被传到了 WriteJSON() 方法中,最终写入数据调用的是 gin.Context.Writer.Write() 方法。gin.ResponseWriter 源码如下

package gin

type Context struct {
   //...
   writermem responseWriter
   Writer    ResponseWriter
   //...
}

// ResponseWriter ...
type ResponseWriter interface {
    //...
    http.ResponseWriter
    //...
}

可以看出 gin.Context.Writer 类型为 interface gin.ResponseWriter。

package http

type ResponseWriter interface {
    //...
    Write([]byte) (int, error)
    //...
}

要实现 gin.ResponseWriter 接口,必须实现Write([]byte) (int, error) 方法。所以写入 response body 调用的是 gin.Context.Writer.Write() 方法,gin.Context.Writer 需要是type gin.ResponseWriter interface 的一个具体实现。

到此,可以看出上面代码示例的思路:

实现 type gin.ResponseWriter interface 并重写 Write([]byte) (int, error) 方法,该方法在实现 gin.Context.Writer.Write() 原有功能的同时,再向一个 bytes.buffer 中写一份数据来用于获取 response body 使用。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

路多辛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值