go讀取request.Body內(nèi)容踩坑實戰(zhàn)記錄
前言
踩坑代碼如下,當(dāng)時是想獲取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)
//發(fā)現(xiàn)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發(fā)現(xiàn)里面并沒有內(nèi)容,進行追查發(fā)現(xiàn)c.Request.Body在第一次經(jīng)過io.ReadAll()調(diào)用后,再次調(diào)用時內(nèi)容已為空。
為什么會這樣??難道io.ReadAll是讀完后就給清空了嗎??
帶著這個問題對底層代碼進行了CR,最終得到答案:不是 ??!
因為從Body.src.R.buf中拷貝,全拷貝完后設(shè)置b.sawEOF為true,再次讀取時遇到這個為true時就不會再讀取。
代碼CR總結(jié)
- Body 字段是一個 io.ReadCloser 類型,io.ReadCloser 類型繼承了 io.Reader 和 io.Closer 兩個接口,其中 io.Reader 接口可以通過 Read 方法讀取到消息體中的內(nèi)容
- io.ReadAll()時會先創(chuàng)建一個切片,初始化容量512,然后開始填充這個切片,中間會有一個巧妙的方式擴容,值得學(xué)習(xí)借鑒。
- 數(shù)據(jù)是從 b.buf(Body.src.R.buf) 中拷貝, n = copy(p, b.buf[b.r:b.w])
- 數(shù)據(jù)循環(huán)拷貝,一直到下面幾種情況會直接返回
- b.sawEOF==true
- b.closed==true
- l.N<=0(l.N指剩余內(nèi)容的數(shù)量,每讀取一段時會減掉)
- 數(shù)據(jù)在copy過程中,會設(shè)置l.N=l.N-n 當(dāng)剩余數(shù)量為0時,會設(shè)置 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提教過來的數(shù)據(jù)
initBuf("duzhenxun")
//可以把數(shù)據(jù)讀出
data1 := readAll()
//這時啥數(shù)據(jù)也沒有
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 語言中,切片本身是一個包含指向底層數(shù)組的指針、長度和容量等信息的結(jié)構(gòu)體,因此對切片進行取子集操作不會創(chuàng)建新的底層數(shù)組,而只是創(chuàng)建了一個新的切片結(jié)構(gòu)體,并更新了其長度和指針等信息。
//因此,可以理解為 b[:len(b)+n]是一個新的切片,并且與原切片 b 共享同一個底層數(shù)組(指針指向相同的底層數(shù)組),但長度和容量等信息可能不同。
}
}
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的相關(guān)代碼
//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的數(shù)量,err信息
n, err := r.Read(b[len(b):cap(b)])
//都讀完后會設(shè)置 body.closed=true,當(dāng)再調(diào)用r.Read時遇到b.closed=true不會再copy數(shù)據(jù),會直接返回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
}
//重點關(guān)注
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
}總結(jié)
到此這篇關(guān)于go讀取request.Body內(nèi)容踩坑的文章就介紹到這了,更多相關(guān)go讀request.Body內(nèi)容內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang并發(fā)編程中Context包的使用與并發(fā)控制
Golang的context包提供了在并發(fā)編程中傳遞取消信號、超時控制和元數(shù)據(jù)的功能,本文就來介紹一下Golang并發(fā)編程中Context包的使用與并發(fā)控制,感興趣的可以了解一下2024-11-11

