Skip to content

Commit

Permalink
Merge pull request Telmate#75 from jmreyes/streaming-upload
Browse files Browse the repository at this point in the history
Add streamed file upload
  • Loading branch information
ggongaware authored May 28, 2020
2 parents dec6fb1 + 6336d37 commit 0d7907f
Showing 1 changed file with 59 additions and 1 deletion.
60 changes: 59 additions & 1 deletion proxmox/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"mime/multipart"
"net"
"net/http"
"os"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -722,7 +723,28 @@ func (c *Client) DeleteVMDisks(
}

func (c *Client) Upload(node string, storage string, contentType string, filename string, file io.Reader) error {
body, mimetype, err := createUploadBody(contentType, filename, file)
var doStreamingIO bool
var fileSize int64
var contentLength int64

if f, ok := file.(*os.File); ok {
doStreamingIO = true
fileInfo, err := f.Stat()
if err != nil {
return err
}
fileSize = fileInfo.Size()
}

var body io.Reader
var mimetype string
var err error

if doStreamingIO {
body, mimetype, contentLength, err = createStreamedUploadBody(contentType, filename, fileSize, file)
} else {
body, mimetype, err = createUploadBody(contentType, filename, file)
}
if err != nil {
return err
}
Expand All @@ -735,6 +757,10 @@ func (c *Client) Upload(node string, storage string, contentType string, filenam
req.Header.Add("Content-Type", mimetype)
req.Header.Add("Accept", "application/json")

if doStreamingIO {
req.ContentLength = contentLength
}

resp, err := c.session.Do(req)
if err != nil {
return err
Expand Down Expand Up @@ -780,6 +806,38 @@ func createUploadBody(contentType string, filename string, r io.Reader) (io.Read
return &buf, w.FormDataContentType(), nil
}

// createStreamedUploadBody - Use MultiReader to create the multipart body from the file reader,
// avoiding allocation of large files in memory before upload (useful e.g. for Windows ISOs).
func createStreamedUploadBody(contentType string, filename string, fileSize int64, r io.Reader) (io.Reader, string, int64, error) {
var buf bytes.Buffer
w := multipart.NewWriter(&buf)

err := w.WriteField("content", contentType)
if err != nil {
return nil, "", 0, err
}

_, err = w.CreateFormFile("filename", filename)
if err != nil {
return nil, "", 0, err
}

headerSize := buf.Len()

err = w.Close()
if err != nil {
return nil, "", 0, err
}

mr := io.MultiReader(bytes.NewReader(buf.Bytes()[:headerSize]),
r,
bytes.NewReader(buf.Bytes()[headerSize:]))

contentLength := int64(buf.Len()) + fileSize

return mr, w.FormDataContentType(), contentLength, nil
}

// getStorageAndVolumeName - Extract disk storage and disk volume, since disk name is saved
// in Proxmox with its storage.
func getStorageAndVolumeName(
Expand Down

0 comments on commit 0d7907f

Please sign in to comment.