aboutsummaryrefslogtreecommitdiff
path: root/weed/operation/upload_content.go
blob: 117da1c1814fb7be64e85d6ef745ba117fc0a575 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package operation

import (
	"bufio"
	"compress/flate"
	"compress/gzip"
	"crypto/rand"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"mime"
	"mime/multipart"
	"net/http"
	"net/textproto"
	"path/filepath"
	"strings"

	"github.com/valyala/fasthttp"

	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/security"
	"github.com/chrislusf/seaweedfs/weed/util"
)

type UploadResult struct {
	Name  string `json:"name,omitempty"`
	Size  uint32 `json:"size,omitempty"`
	Error string `json:"error,omitempty"`
	ETag  string `json:"eTag,omitempty"`
}

var (
	client *http.Client
)

func init() {
	client = &http.Client{Transport: &http.Transport{
		MaxIdleConnsPerHost: 1024,
	}}
}

var fileNameEscaper = strings.NewReplacer("\\", "\\\\", "\"", "\\\"")

// Upload sends a POST request to a volume server to upload the content with adjustable compression level
func UploadWithLocalCompressionLevel(uploadUrl string, filename string, reader io.Reader, isGzipped bool, mtype string, pairMap map[string]string, jwt security.EncodedJwt, compressionLevel int) (*UploadResult, error) {
	if compressionLevel < 1 {
		compressionLevel = 1
	}
	if compressionLevel > 9 {
		compressionLevel = 9
	}
	return doUpload(uploadUrl, filename, reader, isGzipped, mtype, pairMap, compressionLevel, jwt)
}

// Upload sends a POST request to a volume server to upload the content with fast compression
func Upload(uploadUrl string, filename string, reader io.Reader, isGzipped bool, mtype string, pairMap map[string]string, jwt security.EncodedJwt) (*UploadResult, error) {
	return doUpload(uploadUrl, filename, reader, isGzipped, mtype, pairMap, flate.BestSpeed, jwt)
}

func doUpload(uploadUrl string, filename string, reader io.Reader, isGzipped bool, mtype string, pairMap map[string]string, compression int, jwt security.EncodedJwt) (*UploadResult, error) {
	contentIsGzipped := isGzipped
	shouldGzipNow := false
	if !isGzipped {
		if shouldBeZipped, iAmSure := util.IsGzippableFileType(filepath.Base(filename), mtype); iAmSure && shouldBeZipped {
			shouldGzipNow = true
			contentIsGzipped = true
		}
	}
	return upload_content(uploadUrl, func(w io.Writer) (err error) {
		if shouldGzipNow {
			gzWriter, _ := gzip.NewWriterLevel(w, compression)
			_, err = io.Copy(gzWriter, reader)
			gzWriter.Close()
		} else {
			_, err = io.Copy(w, reader)
		}
		return
	}, filename, contentIsGzipped, mtype, pairMap, jwt)
}

func randomBoundary() string {
	var buf [30]byte
	_, err := io.ReadFull(rand.Reader, buf[:])
	if err != nil {
		panic(err)
	}
	return fmt.Sprintf("%x", buf[:])
}

func upload_content(uploadUrl string, fillBufferFunction func(w io.Writer) error, filename string, isGzipped bool, mtype string, pairMap map[string]string, jwt security.EncodedJwt) (*UploadResult, error) {

	if mtype == "" {
		mtype = mime.TypeByExtension(strings.ToLower(filepath.Ext(filename)))
	}
	boundary := randomBoundary()
	contentType := "multipart/form-data; boundary=" + boundary

	var ret UploadResult
	var etag string
	var writeErr error
	err := util.PostContent(uploadUrl, func(w *bufio.Writer) {
		h := make(textproto.MIMEHeader)
		h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, fileNameEscaper.Replace(filename)))
		if mtype != "" {
			h.Set("Content-Type", mtype)
		}
		if isGzipped {
			h.Set("Content-Encoding", "gzip")
		}

		body_writer := multipart.NewWriter(w)
		body_writer.SetBoundary(boundary)
		file_writer, cp_err := body_writer.CreatePart(h)
		if cp_err != nil {
			glog.V(0).Infoln("error creating form file", cp_err.Error())
			writeErr = cp_err
			return
		}
		if err := fillBufferFunction(file_writer); err != nil {
			glog.V(0).Infoln("error copying data", err)
			writeErr = err
			return
		}
		if err := body_writer.Close(); err != nil {
			glog.V(0).Infoln("error closing body", err)
			writeErr = err
			return
		}
		w.Flush()

	}, func(header *fasthttp.RequestHeader) {
		header.Set("Content-Type", contentType)
		for k, v := range pairMap {
			header.Set(k, v)
		}
		if jwt != "" {
			header.Set("Authorization", "BEARER "+string(jwt))
		}
	}, func(resp *fasthttp.Response) error {
		etagBytes := resp.Header.Peek("ETag")
		lenEtagBytes := len(etagBytes)
		if lenEtagBytes > 2 && etagBytes[0] == '"' && etagBytes[lenEtagBytes-1] == '"' {
			etag = string(etagBytes[1 : len(etagBytes)-1])
		}

		unmarshal_err := json.Unmarshal(resp.Body(), &ret)
		if unmarshal_err != nil {
			glog.V(0).Infoln("failing to read upload response", uploadUrl, string(resp.Body()))
			return unmarshal_err
		}
		if ret.Error != "" {
			return errors.New(ret.Error)
		}
		return nil
	})

	if writeErr != nil {
		return nil, writeErr
	}

	ret.ETag = etag
	if err != nil {
		return nil, err
	}
	return &ret, nil
}