golang多次讀取http request body的問題分析
問題起因
使用postman發(fā)送了一個(gè)http請(qǐng)求,對(duì)每個(gè)請(qǐng)求都有一個(gè)對(duì)應(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庫(kù)中定義的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 ... }
請(qǐng)求處理的代碼使用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 { ... } ... }
問題探究
我把這個(gè)問題發(fā)給了gpt
gpt回答說可能是由于Body已經(jīng)被讀取過一次,事實(shí)上,我的代碼之前確實(shí)使用過ReadBody方法讀取了一次:
func ApiHandler2() (error) { input, err := parse.ReadBody(request.Request) ... }
這個(gè)parse.ReadBody是公司的庫(kù)代碼,在此不深入分析
出于好奇,我問了gpt官方庫(kù)中ioutil.ReadAll()方法能否多次讀取Request.Body
gpt回答ReadAll()方法讀取了一次就會(huì)消耗掉Request.Body,不能再次讀取,并提供兩種方法再次讀取:
- 將讀取的Request.Body緩存到一個(gè)變量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ù)的作用是初始化一個(gè)字節(jié)切片緩沖區(qū),不斷調(diào)用Read方法讀取數(shù)據(jù),直到EOF為止
緩沖區(qū)b的初始大小只有512個(gè)字節(jié),如果緩沖區(qū)滿(len(b)==cap(b)),則向b添加一個(gè)0元素觸發(fā)切片的擴(kuò)容機(jī)制,并去掉添加的"0"元素([:len(b)]),之后一直讀取數(shù)據(jù),可能緩沖區(qū)又會(huì)滿,會(huì)繼續(xù)擴(kuò)容的操作,直到讀取到EOF
從對(duì)ReadAll()方法的分析可以得知,使用ReadAll函數(shù)處理數(shù)據(jù)時(shí),內(nèi)存消耗隨著數(shù)據(jù)的增大而增加,處理較大數(shù)據(jù)時(shí),會(huì)觸發(fā)多次擴(kuò)容機(jī)制,需要分配大量?jī)?nèi)存。加載一個(gè)10M的文件,可能就需要50M的內(nèi)存分配
io.Reader 接口中的 Read 方法的定義如下:
type Reader interface { Read(p []byte) (n int, err error) }
這個(gè)方法接收一個(gè)字節(jié)數(shù)組 p 作為參數(shù),返回兩個(gè)值,一個(gè)是 n 表示讀取的字節(jié)數(shù),另一個(gè)是 err 表示可能出現(xiàn)的錯(cuò)誤。不同的數(shù)據(jù)源類型實(shí)現(xiàn)方式不同
Request.Body只能讀取一次的原因是因?yàn)椋诘谝淮螌?duì)其進(jìn)行讀取時(shí),指針已經(jīng)移動(dòng)到了 EOF(End Of File)位置,再次讀取時(shí)就無法再次從頭開始讀取了
題外話
關(guān)于ReadAll()方法消耗內(nèi)存的替代方案有兩種:
- 使用io.ReadFile函數(shù)
- 使用io.Copy函數(shù)
ReadFile函數(shù)定義如下:
func ReadFile(filename string) ([]byte, error){}
傳參為待加載文件的路徑,返回為文件的內(nèi)容
- io.Copy會(huì)拷貝數(shù)據(jù),少于ReadAll的內(nèi)存消耗
以上就是golang多次讀取http request body的問題分析的詳細(xì)內(nèi)容,更多關(guān)于golang多次讀取http request body的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
如何使用騰訊云go sdk 查詢對(duì)象存儲(chǔ)中最新文件
這篇文章主要介紹了使用騰訊云go sdk 查詢對(duì)象存儲(chǔ)中最新文件,這包括如何創(chuàng)建COS客戶端,如何逐頁(yè)檢索對(duì)象列表,并如何對(duì)結(jié)果排序以找到最后更新的對(duì)象,我們還展示了如何優(yōu)化用戶體驗(yàn),通過實(shí)時(shí)進(jìn)度更新和檢索多個(gè)文件來改進(jìn)程序,需要的朋友可以參考下2024-03-03Golang HTTP 服務(wù)平滑重啟及升級(jí)的思路
Golang HTTP服務(wù)在上線時(shí),需要重新編譯可執(zhí)行文件,關(guān)閉正在運(yùn)行的進(jìn)程,然后再啟動(dòng)新的運(yùn)行進(jìn)程。這篇文章主要介紹了Golang HTTP 服務(wù)平滑重啟及升級(jí),需要的朋友可以參考下2020-04-04Go語(yǔ)言編譯程序從后臺(tái)運(yùn)行,不出現(xiàn)dos窗口的操作
這篇文章主要介紹了Go語(yǔ)言編譯程序從后臺(tái)運(yùn)行,不出現(xiàn)dos窗口的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-04-04Golang實(shí)現(xiàn)簡(jiǎn)易的rpc調(diào)用
RPC指(Remote Procedure Call Protocol)遠(yuǎn)程過程調(diào)用協(xié)議。本文將實(shí)現(xiàn)利用Golang進(jìn)行rpc調(diào)用(只實(shí)現(xiàn)一個(gè)rpc框架基本的功能,不對(duì)性能做保證),需要的可以參考一下2023-03-03使用Golang編寫一個(gè)簡(jiǎn)單的命令行工具
Cobra是一個(gè)強(qiáng)大的開源工具,能夠幫助我們快速構(gòu)建出優(yōu)雅且功能豐富的命令行應(yīng)用,本文將利用Cobra編寫一個(gè)簡(jiǎn)單的命令行工具,感興趣的可以了解下2023-12-12Windows10系統(tǒng)下安裝Go環(huán)境詳細(xì)步驟
Go語(yǔ)言是谷歌推出的一款全新的編程語(yǔ)言,可以在不損失應(yīng)用程序性能的情況下極大的降低代碼的復(fù)雜性,這篇文章主要給大家介紹了關(guān)于Windows10系統(tǒng)下安裝Go環(huán)境的詳細(xì)步驟,需要的朋友可以參考下2023-11-11Golang實(shí)現(xiàn)支付寶沙箱支付的方法步驟
本文主要介紹了Golang實(shí)現(xiàn)支付寶沙箱支付的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04淺析Go語(yǔ)言如何在終端里實(shí)現(xiàn)倒計(jì)時(shí)
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中是如何在終端里實(shí)現(xiàn)倒計(jì)時(shí)的,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-03-03