|
1 | 1 | package httpclient |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "bytes" |
5 | 4 | "context" |
6 | 5 | "encoding/base64" |
7 | 6 | "fmt" |
8 | 7 | "io" |
9 | 8 | "mime/multipart" |
10 | 9 | "net/http" |
11 | 10 | "net/textproto" |
12 | | - "net/url" |
13 | 11 | "os" |
14 | 12 | "path/filepath" |
15 | | - "strings" |
16 | 13 | "sync" |
17 | 14 | "time" |
18 | 15 |
|
@@ -307,88 +304,6 @@ func addFormField(writer *multipart.Writer, key, val string, sugar *zap.SugaredL |
307 | 304 | return nil |
308 | 305 | } |
309 | 306 |
|
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 | | - |
367 | | -// setFormDataPartHeader creates a textproto.MIMEHeader for a form data field with the provided field name, file name, content type, and custom headers. |
368 | | -// This function constructs the MIME headers for a multipart form data part, including the content disposition, content type, |
369 | | -// and any custom headers specified. |
370 | | -// Parameters: |
371 | | -// - fieldname: The name of the form field. |
372 | | -// - filename: The name of the file being uploaded (if applicable). |
373 | | -// - contentType: The content type of the form data part (e.g., "image/jpeg"). |
374 | | -// - customHeaders: A map of custom headers to be added to the form data part. The key is the header name and the value is the |
375 | | -// header value. |
376 | | -// |
377 | | -// Returns: |
378 | | -// - textproto.MIMEHeader: The constructed MIME header for the form data part. |
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 | | -// } |
391 | | - |
392 | 307 | // chunkFileUpload reads the file upload into chunks and writes it to the writer. |
393 | 308 | // This function reads the file in chunks and writes it to the provided writer, allowing for progress logging during the upload. |
394 | 309 | // The chunk size is set to 8192 KB (8 MB) by default. This is a common chunk size used for file uploads to cloud storage services. |
@@ -508,111 +423,3 @@ func logUploadProgress(file *os.File, fileSize int64, sugar *zap.SugaredLogger) |
508 | 423 | } |
509 | 424 | } |
510 | 425 | } |
511 | | - |
512 | | -// DoImageMultiPartUpload performs a multipart request with a specifically formatted payload. |
513 | | -// This is designed for APIs that expect a very specific multipart format, where the payload |
514 | | -// needs to be constructed manually rather than using the standard multipart writer. |
515 | | -func (c *Client) DoImageMultiPartUpload(method, endpoint string, fileName string, base64Data string, customBoundary string, out interface{}) (*http.Response, error) { |
516 | | - c.Sugar.Infow("Starting DoImageMultiPartUpload", |
517 | | - zap.String("method", method), |
518 | | - zap.String("endpoint", endpoint), |
519 | | - zap.String("fileName", fileName), |
520 | | - zap.String("boundary", customBoundary)) |
521 | | - |
522 | | - // URL encode the filename for both the Content-Disposition and data prefix |
523 | | - encodedFileName := url.QueryEscape(fileName) |
524 | | - c.Sugar.Debugw("URL encoded filename", zap.String("encodedFileName", encodedFileName)) |
525 | | - |
526 | | - // Construct payload exactly like the example |
527 | | - payload := fmt.Sprintf("%s\r\n"+ |
528 | | - "Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n"+ |
529 | | - "Content-Type: image/png\r\n\r\n"+ |
530 | | - "data:image/png;name=%s;base64,%s\r\n"+ |
531 | | - "%s-", |
532 | | - customBoundary, |
533 | | - encodedFileName, |
534 | | - encodedFileName, |
535 | | - base64Data, |
536 | | - customBoundary) |
537 | | - |
538 | | - // Create truncated version of payload for logging |
539 | | - truncatedPayload := payload |
540 | | - if len(base64Data) > 100 { |
541 | | - // Find the position of base64 data in the payload |
542 | | - base64Start := strings.Index(payload, ";base64,") + 8 |
543 | | - if base64Start > 0 { |
544 | | - truncatedPayload = payload[:base64Start] + "[BASE64_DATA_LENGTH: " + |
545 | | - fmt.Sprintf("%d", len(base64Data)) + "]\r\n" + |
546 | | - customBoundary + "-" |
547 | | - } |
548 | | - } |
549 | | - |
550 | | - c.Sugar.Debugw("Constructed request payload", |
551 | | - zap.String("payload", truncatedPayload)) |
552 | | - |
553 | | - url := (*c.Integration).GetFQDN() + endpoint |
554 | | - c.Sugar.Debugw("Full request URL", zap.String("url", url)) |
555 | | - |
556 | | - // Create request with string payload |
557 | | - req, err := http.NewRequest(method, url, strings.NewReader(payload)) |
558 | | - if err != nil { |
559 | | - c.Sugar.Errorw("Failed to create request", zap.Error(err)) |
560 | | - return nil, fmt.Errorf("failed to create request: %v", err) |
561 | | - } |
562 | | - |
563 | | - // Set headers exactly as in example |
564 | | - req.Header.Set("accept", "application/json") |
565 | | - req.Header.Set("content-type", fmt.Sprintf("multipart/form-data; boundary=%s", strings.TrimPrefix(customBoundary, "---"))) |
566 | | - |
567 | | - c.Sugar.Debugw("Initial headers", |
568 | | - zap.Any("headers", req.Header), |
569 | | - zap.String("accept", req.Header.Get("accept")), |
570 | | - zap.String("content-type", req.Header.Get("content-type"))) |
571 | | - |
572 | | - // Store initial headers |
573 | | - contentType := req.Header.Get("content-type") |
574 | | - accept := req.Header.Get("accept") |
575 | | - |
576 | | - // Apply auth |
577 | | - (*c.Integration).PrepRequestParamsAndAuth(req) |
578 | | - |
579 | | - // Restore and log final headers |
580 | | - req.Header.Set("accept", accept) |
581 | | - req.Header.Set("content-type", contentType) |
582 | | - |
583 | | - c.Sugar.Infow("Final request headers", |
584 | | - zap.Any("headers", req.Header), |
585 | | - zap.String("accept", req.Header.Get("accept")), |
586 | | - zap.String("content-type", req.Header.Get("content-type"))) |
587 | | - |
588 | | - // Send the request |
589 | | - resp, err := c.http.Do(req) |
590 | | - if err != nil { |
591 | | - return nil, fmt.Errorf("failed to send request: %v", err) |
592 | | - } |
593 | | - |
594 | | - c.Sugar.Debugw("Response received", |
595 | | - zap.Int("statusCode", resp.StatusCode), |
596 | | - zap.Any("responseHeaders", resp.Header)) |
597 | | - |
598 | | - // Handle response |
599 | | - if resp.StatusCode >= 200 && resp.StatusCode < 300 { |
600 | | - return resp, response.HandleAPISuccessResponse(resp, out, c.Sugar) |
601 | | - } |
602 | | - |
603 | | - // For error responses, try to log the response body |
604 | | - if resp.Body != nil { |
605 | | - bodyBytes, err := io.ReadAll(resp.Body) |
606 | | - if err != nil { |
607 | | - c.Sugar.Warnw("Failed to read error response body", zap.Error(err)) |
608 | | - } else { |
609 | | - c.Sugar.Errorw("Request failed", |
610 | | - zap.Int("statusCode", resp.StatusCode), |
611 | | - zap.String("responseBody", string(bodyBytes))) |
612 | | - // Create new reader with same data for error handler |
613 | | - resp.Body = io.NopCloser(bytes.NewReader(bodyBytes)) |
614 | | - } |
615 | | - } |
616 | | - |
617 | | - return resp, response.HandleAPIErrorResponse(resp, c.Sugar) |
618 | | -} |
0 commit comments