問題內(nèi)容
我的目標(biāo)是使用 golang 的內(nèi)置 net/http 包將一個大文件上傳到 POST https://somehost/media
。
Api調(diào)用的HTTP格式
POST /media HTTP/1.1 Host: somehost Content-Length: 434 Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW ------WebKitFormBoundary7MA4YWxkTrZu0gW Content-Disposition: form-data; name="detail" More and more detail ------WebKitFormBoundary7MA4YWxkTrZu0gW Content-Disposition: form-data; name="file"; filename="some_big_video.mp4" Content-Type: (data) ------WebKitFormBoundary7MA4YWxkTrZu0gW--
登錄后復(fù)制
在 golang 中,這是代碼。
package main import ( "fmt" "bytes" "mime/multipart" "os" "path/filepath" "io" "net/http" "io/ioutil" ) func main() { url := "https://somehost/media" method := "POST" payload := &bytes.Buffer{} writer := multipart.NewWriter(payload) _ = writer.WriteField("details", "more and more details") file, errFile3 := os.Open("/Users/vajahat/Downloads/some_big_video.mp4") defer file.Close() part3,errFile3 := writer.CreateFormFile("file","some_big_video.mp4") _, errFile3 = io.Copy(part3, file) if errFile3 != nil { fmt.Println(errFile3) return } err := writer.Close() if err != nil { fmt.Println(err) return } client := &http.Client {} req, err := http.NewRequest(method, url, payload) if err != nil { fmt.Println(err) return } req.Header.Set("Content-Type", writer.FormDataContentType()) res, err := client.Do(req) if err != nil { fmt.Println(err) return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { fmt.Println(err) return } fmt.Println(string(body)) }
登錄后復(fù)制
如何避免io.Copy(io.Writer, io.Reader)
問題
上面的代碼工作正常,但是在 _, errFile3 = io.Copy(part3, file)
行上。這實際上將文件中的所有內(nèi)容復(fù)制到主內(nèi)存中。
如何避免這種情況?
有什么辦法,我可以通過 multipart-formdata
將大文件流式傳輸?shù)?api?
該程序?qū)⒃谶h(yuǎn)程服務(wù)器上運行。如果打開一個非常大的文件,可能會崩潰。
正確答案
使用 io.Pipe 和 goroutine 將文件復(fù)制到請求而不加載整個文件內(nèi)存中的文件。
pr, pw := io.Pipe() writer := multipart.NewWriter(pw) ct := writer.FormDataContentType() go func() { _ = writer.WriteField("details", "more and more details") file, err := os.Open("/Users/vajahat/Downloads/some_big_video.mp4") if err != nil { pw.CloseWithError(err) return } defer file.Close() part3, err := writer.CreateFormFile("file", "some_big_video.mp4") if err != nil { pw.CloseWithError(err) return } _, err = io.Copy(part3, file) if err != nil { pw.CloseWithError(err) return } pw.CloseWithError(writer.Close()) }() client := &http.Client{} req, err := http.NewRequest(method, url, pr) if err != nil { fmt.Println(err) return } req.Header.Set("Content-Type", ct) // remaining code as before
登錄后復(fù)制