My objective is to upload a big file to POST https://somehost/media
using golang's builtin net/http package.
HTTP format of the Api call
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: <Content-Type header here>
(data)
------WebKitFormBoundary7MA4YWxkTrZu0gW--
in golang here is the code.
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))
}
How to avoid io.Copy(io.Writer, io.Reader)
problem
The above code is working fine however on the line _, errFile3 = io.Copy(part3, file)
. This literally copies everything from file in to main memory.
How can this be avoided?
Is there any way, i can stream the big file via multipart-formdata
to an api?
This program will be runing on remote server. And that can crash if a very big file is opened.
Use an io.Pipe and a goroutine to copy the file to the request without loading the entire file in memory.
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