golang多次讀取http request body的問題分析
問題起因
使用postman發(fā)送了一個http請求,對每個請求都有一個對應(yīng)的context:
type APIContext struct { Action string ID string Type string Link string Method string Version *APIVersion Request *http.Request Response http.ResponseWriter ... }
其中Request成員變量是golang1.17.3版本http庫中定義的Request結(jié)構(gòu)(這里貼出部分成員變量):
type Request struct { Method string URL *url.URL Header Header Body io.ReadCloser GetBody func() (io.ReadCloser, error) Response *Response ctx context.Context ... }
請求處理的代碼使用ReadAll方法讀取Request.Body,debug發(fā)現(xiàn)讀取出來的字節(jié)切片為空:
func ApiHandler() (error) { ... bodyBytes, err := ioutil.ReadAll(request.Request.Body) // fmt.Printf("bodyBytes: %+v", bodyBytes) 結(jié)果為[] if err != nil { ... } ... }
問題探究
我把這個問題發(fā)給了gpt
gpt回答說可能是由于Body已經(jīng)被讀取過一次,事實上,我的代碼之前確實使用過ReadBody方法讀取了一次:
func ApiHandler2() (error) { input, err := parse.ReadBody(request.Request) ... }
這個parse.ReadBody是公司的庫代碼,在此不深入分析
出于好奇,我問了gpt官方庫中ioutil.ReadAll()方法能否多次讀取Request.Body
gpt回答ReadAll()方法讀取了一次就會消耗掉Request.Body,不能再次讀取,并提供兩種方法再次讀?。?/p>
- 將讀取的Request.Body緩存到一個變量bodyBytes(字節(jié)切片類型),后續(xù)需要讀取
- 使用該變量使用ioutol.NopCloser方法寫回到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. 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 } } }
函數(shù)的作用是初始化一個字節(jié)切片緩沖區(qū),不斷調(diào)用Read方法讀取數(shù)據(jù),直到EOF為止
緩沖區(qū)b的初始大小只有512個字節(jié),如果緩沖區(qū)滿(len(b)==cap(b)),則向b添加一個0元素觸發(fā)切片的擴(kuò)容機(jī)制,并去掉添加的"0"元素([:len(b)]),之后一直讀取數(shù)據(jù),可能緩沖區(qū)又會滿,會繼續(xù)擴(kuò)容的操作,直到讀取到EOF
從對ReadAll()方法的分析可以得知,使用ReadAll函數(shù)處理數(shù)據(jù)時,內(nèi)存消耗隨著數(shù)據(jù)的增大而增加,處理較大數(shù)據(jù)時,會觸發(fā)多次擴(kuò)容機(jī)制,需要分配大量內(nèi)存。加載一個10M的文件,可能就需要50M的內(nèi)存分配
io.Reader 接口中的 Read 方法的定義如下:
type Reader interface { Read(p []byte) (n int, err error) }
這個方法接收一個字節(jié)數(shù)組 p 作為參數(shù),返回兩個值,一個是 n 表示讀取的字節(jié)數(shù),另一個是 err 表示可能出現(xiàn)的錯誤。不同的數(shù)據(jù)源類型實現(xiàn)方式不同
Request.Body只能讀取一次的原因是因為,在第一次對其進(jìn)行讀取時,指針已經(jīng)移動到了 EOF(End Of File)位置,再次讀取時就無法再次從頭開始讀取了
題外話
關(guān)于ReadAll()方法消耗內(nèi)存的替代方案有兩種:
- 使用io.ReadFile函數(shù)
- 使用io.Copy函數(shù)
ReadFile函數(shù)定義如下:
func ReadFile(filename string) ([]byte, error){}
傳參為待加載文件的路徑,返回為文件的內(nèi)容
- io.Copy會拷貝數(shù)據(jù),少于ReadAll的內(nèi)存消耗
以上就是golang多次讀取http request body的問題分析的詳細(xì)內(nèi)容,更多關(guān)于golang多次讀取http request body的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語言編譯程序從后臺運(yùn)行,不出現(xiàn)dos窗口的操作
這篇文章主要介紹了Go語言編譯程序從后臺運(yùn)行,不出現(xiàn)dos窗口的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04Windows10系統(tǒng)下安裝Go環(huán)境詳細(xì)步驟
Go語言是谷歌推出的一款全新的編程語言,可以在不損失應(yīng)用程序性能的情況下極大的降低代碼的復(fù)雜性,這篇文章主要給大家介紹了關(guān)于Windows10系統(tǒng)下安裝Go環(huán)境的詳細(xì)步驟,需要的朋友可以參考下2023-11-11