• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

go - gin框架,body参数只能读取一次问题

武飞扬头像
zzsan
帮助1

背景

使用gin框架, 打算在中间件取body的参数做权限校验, 然后在controller获取参数时, 获取不成功. 用ctx.Copy()也不行

示例代码

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/gin-gonic/gin"
)

type Object struct {
	Id int `json:"id"`
}

func main() {

	router := gin.Default()

	router.POST("/test1", Test)
	router.POST("/test2", TestMiddleware(), Test)
	router.POST("/test3", TestMiddlewareWithRewrite(), Test)
	router.Run(":8000")
}

func TestMiddleware() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		data, err := ctx.GetRawData()
		if err != nil {
			fmt.Println(err.Error())
		}
		fmt.Printf("data: %v\n", string(data))

		m := map[string]int{}
		json.Unmarshal(data, &m)
		fmt.Printf("id: %d\n", m["id"])

		ctx.Next()
	}
}

func TestMiddlewareWithRewrite() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		data, err := ctx.GetRawData()
		if err != nil {
			fmt.Println(err.Error())
		}
		fmt.Printf("data: %v\n", string(data))

		m := map[string]int{}
		json.Unmarshal(data, &m)
		fmt.Printf("id: %d\n", m["id"])

		// rewrite data to body
		ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data))
		ctx.Next()
	}
}

func Test(ctx *gin.Context) {

	var obj Object
	err := ctx.Bind(&obj)
	errStr := ""
	if err != nil {
		errStr = err.Error()
	}
	fmt.Println(err)

	ctx.JSON(http.StatusOK, gin.H{
		"code":  200,
		"msg":   "success",
		"data":  obj,
		"error": errStr,
	})

}
学新通

调用接口

curl -X POST -H "Accept: application/json" -H "Content-type: application/json" -d '{"id":1}' localhost:8000/test1

{
    "code": 200,
    "data": {
        "id": 1
    },
    "error": "",
    "msg": "success"
}

curl -X POST -H "Accept: application/json" -H "Content-type: application/json" -d '{"id":1}' localhost:8000/test2

{
    "code": 200,
    "data": {
        "id": 0
    },
    "error": "EOF",
    "msg": "success"
}

curl -X POST -H "Accept: application/json" -H "Content-type: application/json" -d '{"id":1}' localhost:8000/test3

{
    "code": 200,
    "data": {
        "id": 1
    },
    "error": "",
    "msg": "success"
}

代码解析

参数绑定本质也是从ctx.Request.Body中读取数据, 查看ctx.GetRawData()源码如下:

// GetRawData return stream data.
func (c *Context) GetRawData() ([]byte, error) {
	return ioutil.ReadAll(c.Request.Body)
}

ioutil.ReadAll如下:

// ReadAll reads from r until an error or EOF and returns the data it read.
// A successful call returns err == nil, not err == EOF. Because ReadAll is
// defined to read from src until EOF, it does not treat an EOF from Read
// as an error to be reported.
//
// As of Go 1.16, this function simply calls io.ReadAll.
func ReadAll(r io.Reader) ([]byte, error) {
	return io.ReadAll(r)
}

io.ReadAll如下:

// ReadAll reads from r until an error or EOF and returns the data it read.
// A successful call returns err == nil, not err == EOF. Because ReadAll is
// defined to read from src until EOF, it does not treat an EOF from Read
// as an error to be reported.
func ReadAll(r Reader) ([]byte, error) {
	b := make([]byte, 0, 512)
	for {
		if len(b) == cap(b) {
			// Add more capacity (let append pick how much).
			b = append(b, 0)[:len(b)]
		}
		n, err := r.Read(b[len(b):cap(b)])
		b = b[:len(b) n]
		if err != nil {
			if err == EOF {
				err = nil
			}
			return b, err
		}
	}
}
学新通

Read内容如下:

// Reader is the interface that wraps the basic Read method.
//
// Read reads up to len(p) bytes into p. It returns the number of bytes
// read (0 <= n <= len(p)) and any error encountered. Even if Read
// returns n < len(p), it may use all of p as scratch space during the call.
// If some data is available but not len(p) bytes, Read conventionally
// returns what is available instead of waiting for more.
//
// When Read encounters an error or end-of-file condition after
// successfully reading n > 0 bytes, it returns the number of
// bytes read. It may return the (non-nil) error from the same call
// or return the error (and n == 0) from a subsequent call.
// An instance of this general case is that a Reader returning
// a non-zero number of bytes at the end of the input stream may
// return either err == EOF or err == nil. The next Read should
// return 0, EOF.
//
// Callers should always process the n > 0 bytes returned before
// considering the error err. Doing so correctly handles I/O errors
// that happen after reading some bytes and also both of the
// allowed EOF behaviors.
//
// Implementations of Read are discouraged from returning a
// zero byte count with a nil error, except when len(p) == 0.
// Callers should treat a return of 0 and nil as indicating that
// nothing happened; in particular it does not indicate EOF.
//
// Implementations must not retain p.
type Reader interface {
	Read(p []byte) (n int, err error)
}
学新通

可以看出, ctx.Request.Body的读取, 类似文件读取一样, 读取数据时, 指针会对应移动至EOF, 所以下次读取的时候, seek指针还在EOF处

解决方案

  1. 回写 ctx.Request.Body
    读取完数据时, 回写 ctx.Request.Body
    ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data))
    package main
    
    import (
    	"bytes"
    	"encoding/json"
    	"fmt"
    	"io/ioutil"
    	"net/http"
    
    	"github.com/gin-gonic/gin"
    )
    
    type Object struct {
    	Id int `json:"id"`
    }
    
    func main() {
    
    	router := gin.Default()
    	router.POST("/test", TestMiddlewareWithRewrite(), Test)
    	router.Run(":8000")
    }
    
    func TestMiddlewareWithRewrite() gin.HandlerFunc {
    	return func(ctx *gin.Context) {
    		data, err := ctx.GetRawData()
    		if err != nil {
    			fmt.Println(err.Error())
    		}
    		fmt.Printf("data: %v\n", string(data))
    
    		m := map[string]int{}
    		json.Unmarshal(data, &m)
    		fmt.Printf("id: %d\n", m["id"])
    
    		// rewrite data to body
    		ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data))
    		ctx.Next()
    	}
    }
    
    func Test(ctx *gin.Context) {
    
    	var obj Object
    	err := ctx.Bind(&obj)
    	errStr := ""
    	if err != nil {
    		errStr = err.Error()
    	}
    	fmt.Println(err)
    
    	ctx.JSON(http.StatusOK, gin.H{
    		"code":  200,
    		"msg":   "success",
    		"data":  obj,
    		"error": errStr,
    	})
    
    }
    
    学新通
  2. gin自带函数Set和Get
    读取完, 使用gin自带函数Set和Get
    ctx.Set("test", 111)
    ctx.Get("test")

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgfaiah
系列文章
更多 icon
同类精品
更多 icon
继续加载