問題內容
我的目標是使用 golang 的內置 net/http 包將一個大文件上傳到 POST https://somehost/media 。
Api調用的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--
登錄后復制
在 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))
}
登錄后復制
如何避免io.Copy(io.Writer, io.Reader)問題
上面的代碼工作正常,但是在 _, errFile3 = io.Copy(part3, file) 行上。這實際上將文件中的所有內容復制到主內存中。
如何避免這種情況?
有什么辦法,我可以通過 multipart-formdata 將大文件流式傳輸到 api?
該程序將在遠程服務器上運行。如果打開一個非常大的文件,可能會崩潰。
正確答案
使用 io.Pipe 和 goroutine 將文件復制到請求而不加載整個文件內存中的文件。
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
登錄后復制






