@@ -67,7 +67,11 @@ type UploadState struct {
67
67
// }
68
68
//
69
69
// // Use `result` or `resp` as needed
70
- func (c * Client ) DoMultiPartRequest (method , endpoint string , files map [string ][]string , formDataFields map [string ]string , fileContentTypes map [string ]string , formDataPartHeaders map [string ]http.Header , out interface {}) (* http.Response , error ) {
70
+ func (c * Client ) DoMultiPartRequest (method , endpoint string , files map [string ][]string , formDataFields map [string ]string , fileContentTypes map [string ]string , formDataPartHeaders map [string ]http.Header , encodingType string , out interface {}) (* http.Response , error ) {
71
+ if encodingType != "raw" && encodingType != "base64" {
72
+ c .Sugar .Errorw ("Invalid encoding type specified" , zap .String ("encodingType" , encodingType ))
73
+ return nil , fmt .Errorf ("invalid encoding type: %s. Must be 'raw' or 'base64'" , encodingType )
74
+ }
71
75
72
76
if method != http .MethodPost && method != http .MethodPut {
73
77
c .Sugar .Error ("HTTP method not supported for multipart request" , zap .String ("method" , method ))
@@ -92,20 +96,21 @@ func (c *Client) DoMultiPartRequest(method, endpoint string, files map[string][]
92
96
var body io.Reader
93
97
var contentType string
94
98
95
- // Create multipart body in a function to ensure it runs again on retry
96
99
createBody := func () error {
97
100
var err error
98
- body , contentType , err = createStreamingMultipartRequestBody (files , formDataFields , fileContentTypes , formDataPartHeaders , c .Sugar )
101
+ body , contentType , err = createStreamingMultipartRequestBody (files , formDataFields , fileContentTypes , formDataPartHeaders , encodingType , c .Sugar )
99
102
if err != nil {
100
103
c .Sugar .Errorw ("Failed to create streaming multipart request body" , zap .Error (err ))
101
104
} else {
102
- c .Sugar .Infow ("Successfully created streaming multipart request body" , zap .String ("content_type" , contentType ))
105
+ c .Sugar .Infow ("Successfully created streaming multipart request body" ,
106
+ zap .String ("content_type" , contentType ),
107
+ zap .String ("encoding" , encodingType ))
103
108
}
104
109
return err
105
110
}
106
111
107
112
if err := createBody (); err != nil {
108
- c .Sugar .Errorw ("Failed to create streaming multipart request body" , zap .Error (err ))
113
+ c .Sugar .Errorw ("Failed to create multipart request body" , zap .Error (err ))
109
114
return nil , err
110
115
}
111
116
@@ -115,23 +120,33 @@ func (c *Client) DoMultiPartRequest(method, endpoint string, files map[string][]
115
120
return nil , err
116
121
}
117
122
118
- c .Sugar .Infow ("Created HTTP Multipart request" , zap .String ("method" , method ), zap .String ("url" , url ), zap .String ("content_type" , contentType ))
123
+ c .Sugar .Infow ("Created HTTP Multipart request" ,
124
+ zap .String ("method" , method ),
125
+ zap .String ("url" , url ),
126
+ zap .String ("content_type" , contentType ),
127
+ zap .String ("encoding" , encodingType ))
119
128
120
129
(* c .Integration ).PrepRequestParamsAndAuth (req )
121
-
122
130
req .Header .Set ("Content-Type" , contentType )
123
131
124
132
startTime := time .Now ()
125
133
126
- resp , requestErr := c .http .Do (req )
134
+ resp , err := c .http .Do (req )
127
135
duration := time .Since (startTime )
128
136
129
- if requestErr != nil {
130
- c .Sugar .Errorw ("Failed to send request" , zap .String ("method" , method ), zap .String ("endpoint" , endpoint ), zap .Error (requestErr ))
131
- return nil , requestErr
137
+ if err != nil {
138
+ c .Sugar .Errorw ("Failed to send request" ,
139
+ zap .String ("method" , method ),
140
+ zap .String ("endpoint" , endpoint ),
141
+ zap .Error (err ))
142
+ return nil , err
132
143
}
133
144
134
- c .Sugar .Debugw ("Request sent successfully" , zap .String ("method" , method ), zap .String ("endpoint" , endpoint ), zap .Int ("status_code" , resp .StatusCode ), zap .Duration ("duration" , duration ))
145
+ c .Sugar .Debugw ("Request sent successfully" ,
146
+ zap .String ("method" , method ),
147
+ zap .String ("endpoint" , endpoint ),
148
+ zap .Int ("status_code" , resp .StatusCode ),
149
+ zap .Duration ("duration" , duration ))
135
150
136
151
if resp .StatusCode >= 200 && resp .StatusCode < 300 {
137
152
return resp , response .HandleAPISuccessResponse (resp , out , c .Sugar )
@@ -161,7 +176,7 @@ func (c *Client) DoMultiPartRequest(method, endpoint string, files map[string][]
161
176
// - string: The content type of the multipart request body. This includes the boundary string used by the multipart writer.
162
177
// - error: An error object indicating failure during the construction of the multipart request body. This could be due to issues
163
178
// such as file reading errors or multipart writer errors.
164
- func createStreamingMultipartRequestBody (files map [string ][]string , formDataFields map [string ]string , fileContentTypes map [string ]string , formDataPartHeaders map [string ]http.Header , sugar * zap.SugaredLogger ) (io.Reader , string , error ) {
179
+ func createStreamingMultipartRequestBody (files map [string ][]string , formDataFields map [string ]string , fileContentTypes map [string ]string , formDataPartHeaders map [string ]http.Header , encodingType string , sugar * zap.SugaredLogger ) (io.Reader , string , error ) {
165
180
pr , pw := io .Pipe ()
166
181
writer := multipart .NewWriter (pw )
167
182
@@ -177,8 +192,11 @@ func createStreamingMultipartRequestBody(files map[string][]string, formDataFiel
177
192
178
193
for fieldName , filePaths := range files {
179
194
for _ , filePath := range filePaths {
180
- sugar .Debugw ("Adding file part" , zap .String ("field_name" , fieldName ), zap .String ("file_path" , filePath ))
181
- if err := addFilePart (writer , fieldName , filePath , fileContentTypes , formDataPartHeaders , sugar ); err != nil {
195
+ sugar .Debugw ("Adding file part" ,
196
+ zap .String ("field_name" , fieldName ),
197
+ zap .String ("file_path" , filePath ),
198
+ zap .String ("encoding" , encodingType ))
199
+ if err := addFilePartWithEncoding (writer , fieldName , filePath , fileContentTypes , formDataPartHeaders , encodingType , sugar ); err != nil {
182
200
sugar .Errorw ("Failed to add file part" , zap .Error (err ))
183
201
pw .CloseWithError (err )
184
202
return
@@ -199,47 +217,33 @@ func createStreamingMultipartRequestBody(files map[string][]string, formDataFiel
199
217
return pr , writer .FormDataContentType (), nil
200
218
}
201
219
202
- // addFilePart adds a base64 encoded file part to the multipart writer with the provided field name and file path.
203
- // This function opens the specified file, sets the appropriate content type and headers, and adds it to the multipart writer.
204
- // Parameters:
205
- // - writer: The multipart writer used to construct the multipart request body.
206
- // - fieldName: The field name for the file part.
207
- // - filePath: The path to the file to be included in the request.
208
- // - fileContentTypes: A map specifying the content type for each file part. The key is the field name and the value is the
209
- // content type (e.g., "image/jpeg").
210
- // - formDataPartHeaders: A map specifying custom headers for each part of the multipart form data. The key is the field name
211
- // and the value is an http.Header containing the headers for that part.
212
- // - sugar: An instance of a logger implementing the logger.Logger interface, used to sugar informational messages, warnings,
213
- // and errors encountered during the addition of the file part.
214
- //
215
- // Returns:
216
- // - error: An error object indicating failure during the addition of the file part. This could be due to issues such as
217
- // file reading errors or multipart writer errors.
218
- func addFilePart (writer * multipart.Writer , fieldName , filePath string , fileContentTypes map [string ]string , formDataPartHeaders map [string ]http.Header , sugar * zap.SugaredLogger ) error {
220
+ // addFilePartWithEncoding adds a file part to the multipart writer with specified encoding.
221
+ // Supports both raw file content and base64 encoding based on encodingType parameter.
222
+ func addFilePartWithEncoding (writer * multipart.Writer , fieldName , filePath string , fileContentTypes map [string ]string , formDataPartHeaders map [string ]http.Header , encodingType string , sugar * zap.SugaredLogger ) error {
219
223
file , err := os .Open (filePath )
220
224
if err != nil {
221
225
sugar .Errorw ("Failed to open file" , zap .String ("filePath" , filePath ), zap .Error (err ))
222
226
return err
223
227
}
224
228
defer file .Close ()
225
229
226
- // Default fileContentType
227
230
contentType := "application/octet-stream"
228
231
if ct , ok := fileContentTypes [fieldName ]; ok {
229
232
contentType = ct
230
233
}
231
234
232
- header := setFormDataPartHeader (fieldName , filepath .Base (filePath ), contentType , formDataPartHeaders [fieldName ])
235
+ header := createFilePartHeader (fieldName , filePath , contentType , formDataPartHeaders [fieldName ], encodingType )
236
+ sugar .Debugw ("Created file part header" ,
237
+ zap .String ("fieldName" , fieldName ),
238
+ zap .String ("contentType" , contentType ),
239
+ zap .String ("encoding" , encodingType ))
233
240
234
241
part , err := writer .CreatePart (header )
235
242
if err != nil {
236
243
sugar .Errorw ("Failed to create form file part" , zap .String ("fieldName" , fieldName ), zap .Error (err ))
237
244
return err
238
245
}
239
246
240
- encoder := base64 .NewEncoder (base64 .StdEncoding , part )
241
- defer encoder .Close ()
242
-
243
247
fileSize , err := file .Stat ()
244
248
if err != nil {
245
249
sugar .Errorw ("Failed to get file info" , zap .String ("filePath" , filePath ), zap .Error (err ))
@@ -248,12 +252,34 @@ func addFilePart(writer *multipart.Writer, fieldName, filePath string, fileConte
248
252
249
253
progressLogger := logUploadProgress (file , fileSize .Size (), sugar )
250
254
uploadState := & UploadState {}
251
- if err := chunkFileUpload (file , encoder , progressLogger , uploadState , sugar ); err != nil {
252
- sugar .Errorw ("Failed to copy file content" , zap .String ("filePath" , filePath ), zap .Error (err ))
253
- return err
255
+
256
+ var writeTarget io.Writer = part
257
+ if encodingType == "base64" {
258
+ encoder := base64 .NewEncoder (base64 .StdEncoding , part )
259
+ defer encoder .Close ()
260
+ writeTarget = encoder
261
+ sugar .Debugw ("Using base64 encoding for file upload" , zap .String ("fieldName" , fieldName ))
262
+ } else {
263
+ sugar .Debugw ("Using raw encoding for file upload" , zap .String ("fieldName" , fieldName ))
254
264
}
255
265
256
- return nil
266
+ return chunkFileUpload (file , writeTarget , progressLogger , uploadState , sugar )
267
+ }
268
+
269
+ // createFilePartHeader creates the MIME header for a file part with the specified encoding type.
270
+ func createFilePartHeader (fieldname , filename , contentType string , customHeaders http.Header , encodingType string ) textproto.MIMEHeader {
271
+ header := textproto.MIMEHeader {}
272
+ header .Set ("Content-Disposition" , fmt .Sprintf (`form-data; name="%s"; filename="%s"` , fieldname , filepath .Base (filename )))
273
+ header .Set ("Content-Type" , contentType )
274
+ if encodingType == "base64" {
275
+ header .Set ("Content-Transfer-Encoding" , "base64" )
276
+ }
277
+ for key , values := range customHeaders {
278
+ for _ , value := range values {
279
+ header .Add (key , value )
280
+ }
281
+ }
282
+ return header
257
283
}
258
284
259
285
// addFormField adds a form field to the multipart writer with the provided key and value.
@@ -281,6 +307,63 @@ func addFormField(writer *multipart.Writer, key, val string, sugar *zap.SugaredL
281
307
return nil
282
308
}
283
309
310
+ // addFilePart adds a base64 encoded file part to the multipart writer with the provided field name and file path.
311
+ // This function opens the specified file, sets the appropriate content type and headers, and adds it to the multipart writer.
312
+ // Parameters:
313
+ // - writer: The multipart writer used to construct the multipart request body.
314
+ // - fieldName: The field name for the file part.
315
+ // - filePath: The path to the file to be included in the request.
316
+ // - fileContentTypes: A map specifying the content type for each file part. The key is the field name and the value is the
317
+ // content type (e.g., "image/jpeg").
318
+ // - formDataPartHeaders: A map specifying custom headers for each part of the multipart form data. The key is the field name
319
+ // and the value is an http.Header containing the headers for that part.
320
+ // - sugar: An instance of a logger implementing the logger.Logger interface, used to sugar informational messages, warnings,
321
+ // and errors encountered during the addition of the file part.
322
+ //
323
+ // Returns:
324
+ // - error: An error object indicating failure during the addition of the file part. This could be due to issues such as
325
+ // file reading errors or multipart writer errors.
326
+ // func addFilePart(writer *multipart.Writer, fieldName, filePath string, fileContentTypes map[string]string, formDataPartHeaders map[string]http.Header, sugar *zap.SugaredLogger) error {
327
+ // file, err := os.Open(filePath)
328
+ // if err != nil {
329
+ // sugar.Errorw("Failed to open file", zap.String("filePath", filePath), zap.Error(err))
330
+ // return err
331
+ // }
332
+ // defer file.Close()
333
+
334
+ // // Default fileContentType
335
+ // contentType := "application/octet-stream"
336
+ // if ct, ok := fileContentTypes[fieldName]; ok {
337
+ // contentType = ct
338
+ // }
339
+
340
+ // header := setFormDataPartHeader(fieldName, filepath.Base(filePath), contentType, formDataPartHeaders[fieldName])
341
+
342
+ // part, err := writer.CreatePart(header)
343
+ // if err != nil {
344
+ // sugar.Errorw("Failed to create form file part", zap.String("fieldName", fieldName), zap.Error(err))
345
+ // return err
346
+ // }
347
+
348
+ // encoder := base64.NewEncoder(base64.StdEncoding, part)
349
+ // defer encoder.Close()
350
+
351
+ // fileSize, err := file.Stat()
352
+ // if err != nil {
353
+ // sugar.Errorw("Failed to get file info", zap.String("filePath", filePath), zap.Error(err))
354
+ // return err
355
+ // }
356
+
357
+ // progressLogger := logUploadProgress(file, fileSize.Size(), sugar)
358
+ // uploadState := &UploadState{}
359
+ // if err := chunkFileUpload(file, encoder, progressLogger, uploadState, sugar); err != nil {
360
+ // sugar.Errorw("Failed to copy file content", zap.String("filePath", filePath), zap.Error(err))
361
+ // return err
362
+ // }
363
+
364
+ // return nil
365
+ // }
366
+
284
367
// setFormDataPartHeader creates a textproto.MIMEHeader for a form data field with the provided field name, file name, content type, and custom headers.
285
368
// This function constructs the MIME headers for a multipart form data part, including the content disposition, content type,
286
369
// and any custom headers specified.
@@ -293,18 +376,18 @@ func addFormField(writer *multipart.Writer, key, val string, sugar *zap.SugaredL
293
376
//
294
377
// Returns:
295
378
// - textproto.MIMEHeader: The constructed MIME header for the form data part.
296
- func setFormDataPartHeader (fieldname , filename , contentType string , customHeaders http.Header ) textproto.MIMEHeader {
297
- header := textproto.MIMEHeader {}
298
- header .Set ("Content-Disposition" , fmt .Sprintf (`form-data; name="%s"; filename="%s"` , fieldname , filename ))
299
- header .Set ("Content-Type" , contentType )
300
- header .Set ("Content-Transfer-Encoding" , "base64" )
301
- for key , values := range customHeaders {
302
- for _ , value := range values {
303
- header .Add (key , value )
304
- }
305
- }
306
- return header
307
- }
379
+ // func setFormDataPartHeader(fieldname, filename, contentType string, customHeaders http.Header) textproto.MIMEHeader {
380
+ // header := textproto.MIMEHeader{}
381
+ // header.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, fieldname, filename))
382
+ // header.Set("Content-Type", contentType)
383
+ // header.Set("Content-Transfer-Encoding", "base64")
384
+ // for key, values := range customHeaders {
385
+ // for _, value := range values {
386
+ // header.Add(key, value)
387
+ // }
388
+ // }
389
+ // return header
390
+ // }
308
391
309
392
// chunkFileUpload reads the file upload into chunks and writes it to the writer.
310
393
// This function reads the file in chunks and writes it to the provided writer, allowing for progress logging during the upload.
0 commit comments