go读request.Body内容踩坑记

news/2024/10/23 7:39:27/

go读request.Body内容踩坑记

踩坑代码如下,当时是想获取body传过来的json

func demo(c *httpserver.Context) {type ReqData struct {Id      int        `json:"id" validate:"required" schema:"id"`Title   string     `json:"title"  validate:"required" schema:"title"`Content [][]string `json:"content" validate:"required" schema:"content"`}bodyByte, _ := io.ReadAll(c.Request.Body)fmt.Println(string(bodyByte))var req ReqDataerr := c.Bind(c.Request, &req)//发现req里的属性还是空if err != nil {c.JSONAbort(nil, code.SetErrMsg(err.Error()))return}contentByte, _ := json.Marshal(req.Content)data := svc.table2DataUpdate(c.Ctx, req.Id, req.Title, req.Content) c.JSON(data, err)
}

如上代码Bind发现里面并没有内容,进行追查发现c.Request.Body在第一次经过io.ReadAll()调用后,再次调用时内容已为空。

为什么会这样??难道io.ReadAll是读完后就给清空了吗??

带着这个问题对底层代码进行了CR,最终得到答案:不是 !!

因为从Body.src.R.buf中拷贝,全拷贝完后设置b.sawEOF为true,再次读取时遇到这个为true时就不会再读取。

代码CR总结

  1. Body 字段是一个 io.ReadCloser 类型,io.ReadCloser 类型继承了 io.Reader 和 io.Closer 两个接口,其中 io.Reader 接口可以通过 Read 方法读取到消息体中的内容
  2. io.ReadAll()时会先创建一个切片,初始化容量512,然后开始填充这个切片,中间会有一个巧妙的方式扩容,值得学习借鉴。
  3. 数据是从 b.buf(Body.src.R.buf) 中拷贝, n = copy(p, b.buf[b.r:b.w])
  4. 数据循环拷贝,一直到下面几种情况会直接返回
    • b.sawEOF==true
    • b.closed==true
    • l.N<=0(l.N指剩余内容的数量,每读取一段时会减掉)
  5. 数据在copy过程中,会设置l.N=l.N-n 当剩余数量为0时,会设置 b.sawEOF=true

模拟一个简单的代码

package mainimport ("bytes""errors""fmt"
)type BufDemo struct {buf *bytes.Bufferw   intr   int
}var bf BufDemofunc main() {//初始化一个buf,模拟post提教过来的数据initBuf("duzhenxun")//可以把数据读出data1 := readAll()//这时啥数据也没有data2 := readAll()fmt.Println(data1, data2)
}func readAll() []byte {b := make([]byte, 0, 2)for {if len(b) == cap(b) {//扩容操作b = append(b, 0)[:len(b)]}n, err := read(b[len(b):cap(b)])if err != nil && err.Error() == "EOF" {return b}//这行代码能理解吗??b = b[:len(b)+n]//	b[:len(b)+n] 表示对切片 b 进行取子集操作,并返回一个新的切片。这个新的切片中包含从切片的起始元素开始,到第2个元素(不包括第2个元素)的所有元素。//在 Go 语言中,切片本身是一个包含指向底层数组的指针、长度和容量等信息的结构体,因此对切片进行取子集操作不会创建新的底层数组,而只是创建了一个新的切片结构体,并更新了其长度和指针等信息。//因此,可以理解为 b[:len(b)+n]是一个新的切片,并且与原切片 b 共享同一个底层数组(指针指向相同的底层数组),但长度和容量等信息可能不同。}
}func read(p []byte) (n int, err error) {if bf.r == bf.w {return 0, errors.New("EOF")}n = copy(p, bf.buf.Bytes()[bf.r:bf.w])bf.r += nreturn n, nil
}func initBuf(str string) {bf = BufDemo{buf: bytes.NewBuffer([]byte(str)),r:   0,w:   len(str),}
}

下面为CR的相关代码

//src/io/io.go:626
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)]}//这里是重点,返回copy的数量,err信息n, err := r.Read(b[len(b):cap(b)])//都读完后会设置 body.closed=true,当再调用r.Read时遇到b.closed=true不会再copy数据,会直接返回n=0,err="http: invalid Read on closed Body"b = b[:len(b)+n]if err != nil {if err == EOF {err = nil}return b, err}}
}//r.Read(b[len(b):cap(b)])
//src/net/http/transfer.go:829
func (b *body) Read(p []byte) (n int, err error) {b.mu.Lock()defer b.mu.Unlock()if b.closed {return 0, ErrBodyReadAfterClose}return b.readLocked(p)
}//b.readLocked(p)
//src/net/http/transfer.go:839
// Must hold b.mu.
func (b *body) readLocked(p []byte) (n int, err error) {if b.sawEOF {return 0, io.EOF}//重点关注n, err = b.src.Read(p)if err == io.EOF {b.sawEOF = true// Chunked case. Read the trailer.if b.hdr != nil {if e := b.readTrailer(); e != nil {err = e// Something went wrong in the trailer, we must not allow any// further reads of any kind to succeed from body, nor any// subsequent requests on the server connection. See// golang.org/issue/12027b.sawEOF = falseb.closed = true}b.hdr = nil} else {// If the server declared the Content-Length, our body is a LimitedReader// and we need to check whether this EOF arrived early.if lr, ok := b.src.(*io.LimitedReader); ok && lr.N > 0 {err = io.ErrUnexpectedEOF}}}// If we can return an EOF here along with the read data, do// so. This is optional per the io.Reader contract, but doing// so helps the HTTP transport code recycle its connection// earlier (since it will see this EOF itself), even if the// client doesn't do future reads or Close.if err == nil && n > 0 {if lr, ok := b.src.(*io.LimitedReader); ok && lr.N == 0 {err = io.EOFb.sawEOF = true}}if b.sawEOF && b.onHitEOF != nil {b.onHitEOF()}return n, err
}//b.src.Read 
//src/io/io.go:466
func (l *LimitedReader) Read(p []byte) (n int, err error) {if l.N <= 0 {return 0, EOF}if int64(len(p)) > l.N {p = p[0:l.N]}n, err = l.R.Read(p)l.N -= int64(n)return
}//l.R.Read(p)
//src/bufio/buffio.go:198// Read reads data into p.
// It returns the number of bytes read into p.
// The bytes are taken from at most one Read on the underlying Reader,
// hence n may be less than len(p).
// To read exactly len(p) bytes, use io.ReadFull(b, p).
// At EOF, the count will be zero and err will be io.EOF.
func (b *Reader) Read(p []byte) (n int, err error) {n = len(p)if n == 0 {if b.Buffered() > 0 {return 0, nil}return 0, b.readErr()}if b.r == b.w {if b.err != nil {return 0, b.readErr()}if len(p) >= len(b.buf) {// Large read, empty buffer.// Read directly into p to avoid copy.n, b.err = b.rd.Read(p)if n < 0 {panic(errNegativeRead)}if n > 0 {b.lastByte = int(p[n-1])b.lastRuneSize = -1}return n, b.readErr()}// One read.// Do not use b.fill, which will loop.b.r = 0b.w = 0n, b.err = b.rd.Read(b.buf)if n < 0 {panic(errNegativeRead)}if n == 0 {return 0, b.readErr()}b.w += n}// copy as much as we cann = copy(p, b.buf[b.r:b.w])b.r += nb.lastByte = int(b.buf[b.r-1])b.lastRuneSize = -1return n, nil
}



http://www.ppmy.cn/news/63108.html

相关文章

xib替代main.storyboard

xib替代main.storyboard 其实xib和storyboard在编译时都会变成nib文件。 删除storyboard 删除main.storyboard和ViewController 创建新VC 因为上一步干脆删掉了自带的ViewController&#xff0c;所以这里创建一个新的VC。 创建 创建自定义VC&#xff0c;叫做“TestXibVi…

《Linux 内核设计与实现》12. 内存管理

文章目录 页区获得页获得填充为 0 的页释放页 kmalloc()gfp_mask 标志kfree()vmalloc() slab 层slab 层的设计slab 分配器的接口 在栈上的静态分配单页内核栈 高端内存的映射永久映射临时映射 每个 CPU 的分配新的每个 CPU 接口 页 struct page 结构表示系统中的物理页&#x…

Rosetta从头蛋白抗体设计、结构优化及在药物研发中的应用

Rosetta从头蛋白抗体设计、结构优化及在药物研发中的应用 第一天 内容 主要知识点 从蛋白质折叠到蛋白质设计教学目标&#xff1a;了解本方向内容、理论基础、研究意义。 蛋白质折叠与结构预测简介 主链二面角与二级结构侧链堆积与三级结构蛋白质设计简介 蛋白质设计的分…

【RabbitMQ】SpringAMQP

RabbitMQ 1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得到响应&am…

微服务学习笔记--(Ribbon)

Ribbon负载均衡 负载均衡策略懒加载 Ribbon-负载均衡策略 Ribbon的负载均衡规则是一个叫做IRule的接口来定义的&#xff0c;每一个子接口都是一种规则&#xff1a; IRuleAbstractLoadBalancerRuleRetryRuleClientConfigEnabledRoundRobinRuleRoundRobinRuleRandomRuleBestAv…

【1419. 数青蛙】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一个字符串 croakOfFrogs&#xff0c;它表示不同青蛙发出的蛙鸣声&#xff08;字符串 "croak" &#xff09;的组合。由于同一时间可以有多只青蛙呱呱作响&#xff0c;所以 croakOfFrog…

windeployqt工具打包C++ QT项目

目录 前言方法TIP 前言 使用VS编写好QT项目后&#xff0c;有时需要发送给他人进行测试。在此情况下&#xff0c;发送所有项目文件显然不可取&#xff0c;因为exe文件不能独立运行&#xff0c;故在测试前需要先配置项目环境&#xff0c;以确保运行所需的库文件能够完全。 因此&…

PAT A1042 Shuffling Machine

1042 Shuffling Machine 分数 20 作者 CHEN, Yue 单位 浙江大学 Shuffling is a procedure used to randomize a deck of playing cards. Because standard shuffling techniques are seen as weak, and in order to avoid "inside jobs" where employees collab…