go讀取request.Body內(nèi)容踩坑實(shí)戰(zhàn)記錄
前言
踩坑代碼如下,當(dāng)時(shí)是想獲取body傳過(guò)來(lái)的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)里面并沒(méi)有內(nèi)容,進(jìn)行追查發(fā)現(xiàn)c.Request.Body在第一次經(jīng)過(guò)io.ReadAll()調(diào)用后,再次調(diào)用時(shí)內(nèi)容已為空。
為什么會(huì)這樣??難道io.ReadAll是讀完后就給清空了嗎??
帶著這個(gè)問(wèn)題對(duì)底層代碼進(jìn)行了CR,最終得到答案:不是 ??!
因?yàn)閺腂ody.src.R.buf中拷貝,全拷貝完后設(shè)置b.sawEOF為true,再次讀取時(shí)遇到這個(gè)為true時(shí)就不會(huì)再讀取。
代碼CR總結(jié)
- Body 字段是一個(gè) io.ReadCloser 類型,io.ReadCloser 類型繼承了 io.Reader 和 io.Closer 兩個(gè)接口,其中 io.Reader 接口可以通過(guò) Read 方法讀取到消息體中的內(nèi)容
- io.ReadAll()時(shí)會(huì)先創(chuàng)建一個(gè)切片,初始化容量512,然后開(kāi)始填充這個(gè)切片,中間會(huì)有一個(gè)巧妙的方式擴(kuò)容,值得學(xué)習(xí)借鑒。
- 數(shù)據(jù)是從 b.buf(Body.src.R.buf) 中拷貝, n = copy(p, b.buf[b.r:b.w])
- 數(shù)據(jù)循環(huán)拷貝,一直到下面幾種情況會(huì)直接返回
- b.sawEOF==true
- b.closed==true
- l.N<=0(l.N指剩余內(nèi)容的數(shù)量,每讀取一段時(shí)會(huì)減掉)
- 數(shù)據(jù)在copy過(guò)程中,會(huì)設(shè)置l.N=l.N-n 當(dāng)剩余數(shù)量為0時(shí),會(huì)設(shè)置 b.sawEOF=true
模擬一個(gè)簡(jiǎn)單的代碼
package main import ( "bytes" "errors" "fmt" ) type BufDemo struct { buf *bytes.Buffer w int r int } var bf BufDemo func main() { //初始化一個(gè)buf,模擬post提教過(guò)來(lái)的數(shù)據(jù) initBuf("duzhenxun") //可以把數(shù)據(jù)讀出 data1 := readAll() //這時(shí)啥數(shù)據(jù)也沒(méi)有 data2 := readAll() fmt.Println(data1, data2) } func readAll() []byte { b := make([]byte, 0, 2) for { if len(b) == cap(b) { //擴(kuò)容操作 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] 表示對(duì)切片 b 進(jìn)行取子集操作,并返回一個(gè)新的切片。這個(gè)新的切片中包含從切片的起始元素開(kāi)始,到第2個(gè)元素(不包括第2個(gè)元素)的所有元素。 //在 Go 語(yǔ)言中,切片本身是一個(gè)包含指向底層數(shù)組的指針、長(zhǎng)度和容量等信息的結(jié)構(gòu)體,因此對(duì)切片進(jìn)行取子集操作不會(huì)創(chuàng)建新的底層數(shù)組,而只是創(chuàng)建了一個(gè)新的切片結(jié)構(gòu)體,并更新了其長(zhǎng)度和指針等信息。 //因此,可以理解為 b[:len(b)+n]是一個(gè)新的切片,并且與原切片 b 共享同一個(gè)底層數(shù)組(指針指向相同的底層數(shù)組),但長(zhǎng)度和容量等信息可能不同。 } } 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)] } //這里是重點(diǎn),返回copy的數(shù)量,err信息 n, err := r.Read(b[len(b):cap(b)]) //都讀完后會(huì)設(shè)置 body.closed=true,當(dāng)再調(diào)用r.Read時(shí)遇到b.closed=true不會(huì)再copy數(shù)據(jù),會(huì)直接返回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 } //重點(diǎn)關(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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang并發(fā)編程中Context包的使用與并發(fā)控制
Golang的context包提供了在并發(fā)編程中傳遞取消信號(hào)、超時(shí)控制和元數(shù)據(jù)的功能,本文就來(lái)介紹一下Golang并發(fā)編程中Context包的使用與并發(fā)控制,感興趣的可以了解一下2024-11-11Go語(yǔ)言題解LeetCode下一個(gè)更大元素示例詳解
這篇文章主要為大家介紹了Go語(yǔ)言題解LeetCode下一個(gè)更大元素示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12GoLang基于zap日志庫(kù)的封裝過(guò)程詳解
Zap是我個(gè)人比較喜歡的日志庫(kù),是uber開(kāi)源的,有較好的性能,在項(xiàng)目開(kāi)發(fā)中,經(jīng)常需要把程序運(yùn)行過(guò)程中各種信息記錄下來(lái),有了詳細(xì)的日志有助于問(wèn)題排查和功能優(yōu)化,這篇文章主要介紹了GoLang基于zap日志庫(kù)的封裝過(guò)程,想要詳細(xì)了解可以參考下文2023-05-05golang反向代理設(shè)置host不生效的問(wèn)題解決
在使用golang的httputil做反向代理的時(shí)候,發(fā)現(xiàn)一個(gè)奇怪的現(xiàn)象,上游網(wǎng)關(guān)必須要設(shè)置host才行,不設(shè)置host的話,golang服務(wù)反向代理請(qǐng)求下游會(huì)出現(xiàn)http 503錯(cuò)誤,接下來(lái)通過(guò)本文給大家介紹golang反向代理設(shè)置host不生效問(wèn)題,感興趣的朋友一起看看吧2023-05-05