go读取request.Body内容踩坑实战记录
作者:duzhenxun
很多初学者在使用Go语言进行Web开发时,都会遇到读取 request.Body内容的问题,这篇文章主要给大家介绍了关于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 ReqData err := 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总结
- Body 字段是一个 io.ReadCloser 类型,io.ReadCloser 类型继承了 io.Reader 和 io.Closer 两个接口,其中 io.Reader 接口可以通过 Read 方法读取到消息体中的内容
- io.ReadAll()时会先创建一个切片,初始化容量512,然后开始填充这个切片,中间会有一个巧妙的方式扩容,值得学习借鉴。
- 数据是从 b.buf(Body.src.R.buf) 中拷贝, n = copy(p, b.buf[b.r:b.w])
- 数据循环拷贝,一直到下面几种情况会直接返回
- b.sawEOF==true
- b.closed==true
- l.N<=0(l.N指剩余内容的数量,每读取一段时会减掉)
- 数据在copy过程中,会设置l.N=l.N-n 当剩余数量为0时,会设置 b.sawEOF=true
模拟一个简单的代码
package main import ( "bytes" "errors" "fmt" ) type BufDemo struct { buf *bytes.Buffer w int r int } var bf BufDemo func 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 += n return 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/12027 b.sawEOF = false b.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.EOF b.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 = 0 b.w = 0 n, 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 can n = copy(p, b.buf[b.r:b.w]) b.r += n b.lastByte = int(b.buf[b.r-1]) b.lastRuneSize = -1 return n, nil }
总结
到此这篇关于go读取request.Body内容踩坑的文章就介绍到这了,更多相关go读request.Body内容内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!