Skip to content

Commit 1bb942c

Browse files
authored
Merge pull request #270 from deploymenttheory/add-icon-upload-handling
feat: enhance DoImageMultiPartUpload with URL encoding and improved l…
2 parents b73c364 + 372f5a0 commit 1bb942c

File tree

1 file changed

+62
-67
lines changed

1 file changed

+62
-67
lines changed

httpclient/multipartrequest.go

Lines changed: 62 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package httpclient
22

33
import (
4+
"bytes"
45
"context"
56
"encoding/base64"
67
"fmt"
78
"io"
89
"mime/multipart"
910
"net/http"
1011
"net/textproto"
12+
"net/url"
1113
"os"
1214
"path/filepath"
1315
"strings"
@@ -432,109 +434,102 @@ func (c *Client) DoImageMultiPartUpload(method, endpoint string, fileName string
432434
zap.String("method", method),
433435
zap.String("endpoint", endpoint),
434436
zap.String("fileName", fileName),
435-
zap.String("originalBoundary", customBoundary))
437+
zap.String("boundary", customBoundary))
436438

437-
// Remove any leading hyphens from the boundary when setting in header
438-
cleanBoundary := strings.TrimPrefix(customBoundary, "-----")
439-
c.Sugar.Debugw("Processed boundary",
440-
zap.String("cleanBoundary", cleanBoundary))
439+
// URL encode the filename for both the Content-Disposition and data prefix
440+
encodedFileName := url.QueryEscape(fileName)
441+
c.Sugar.Debugw("URL encoded filename", zap.String("encodedFileName", encodedFileName))
441442

442-
// Format the multipart payload with the specified boundary
443+
// Construct payload exactly like the example
443444
payload := fmt.Sprintf("%s\r\n"+
444445
"Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n"+
445446
"Content-Type: image/png\r\n\r\n"+
446447
"data:image/png;name=%s;base64,%s\r\n"+
447-
"%s--",
448+
"%s-",
448449
customBoundary,
449-
fileName,
450-
fileName,
450+
encodedFileName,
451+
encodedFileName,
451452
base64Data,
452453
customBoundary)
453454

454-
// Log the payload structure (not the full base64 data)
455-
payloadPreview := fmt.Sprintf("%s\r\n"+
456-
"Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n"+
457-
"Content-Type: image/png\r\n\r\n"+
458-
"data:image/png;name=%s;base64,[BASE64_DATA_LENGTH: %d]\r\n"+
459-
"%s--",
460-
customBoundary,
461-
fileName,
462-
fileName,
463-
len(base64Data),
464-
customBoundary)
455+
// Create truncated version of payload for logging
456+
truncatedPayload := payload
457+
if len(base64Data) > 100 {
458+
// Find the position of base64 data in the payload
459+
base64Start := strings.Index(payload, ";base64,") + 8
460+
if base64Start > 0 {
461+
truncatedPayload = payload[:base64Start] + "[BASE64_DATA_LENGTH: " +
462+
fmt.Sprintf("%d", len(base64Data)) + "]\r\n" +
463+
customBoundary + "-"
464+
}
465+
}
465466

466-
c.Sugar.Debugw("Constructed payload",
467-
zap.String("payloadStructure", payloadPreview),
468-
zap.Int("totalPayloadLength", len(payload)))
467+
c.Sugar.Debugw("Constructed request payload",
468+
zap.String("payload", truncatedPayload))
469469

470470
url := (*c.Integration).GetFQDN() + endpoint
471-
c.Sugar.Debugw("Constructed URL", zap.String("fullURL", url))
471+
c.Sugar.Debugw("Full request URL", zap.String("url", url))
472472

473-
// Create the request with the formatted payload
473+
// Create request with string payload
474474
req, err := http.NewRequest(method, url, strings.NewReader(payload))
475475
if err != nil {
476-
c.Sugar.Errorw("Failed to create request",
477-
zap.Error(err),
478-
zap.String("method", method),
479-
zap.String("url", url))
476+
c.Sugar.Errorw("Failed to create request", zap.Error(err))
480477
return nil, fmt.Errorf("failed to create request: %v", err)
481478
}
482479

483-
// Clear existing headers and set only what we need
484-
req.Header = http.Header{}
485-
contentTypeHeader := fmt.Sprintf("multipart/form-data; boundary=---%s", cleanBoundary)
486-
req.Header.Set("Accept", "application/json")
487-
req.Header.Set("Content-Type", contentTypeHeader)
480+
// Set headers exactly as in example
481+
req.Header.Set("accept", "application/json")
482+
req.Header.Set("content-type", fmt.Sprintf("multipart/form-data; boundary=%s", strings.TrimPrefix(customBoundary, "---")))
488483

489-
c.Sugar.Debugw("Request headers before auth",
484+
c.Sugar.Debugw("Initial headers",
490485
zap.Any("headers", req.Header),
491-
zap.String("contentType", contentTypeHeader))
486+
zap.String("accept", req.Header.Get("accept")),
487+
zap.String("content-type", req.Header.Get("content-type")))
492488

493-
preservedAccept := req.Header.Get("Accept")
494-
preservedContentType := req.Header.Get("Content-Type")
489+
// Store initial headers
490+
contentType := req.Header.Get("content-type")
491+
accept := req.Header.Get("accept")
495492

493+
// Apply auth
496494
(*c.Integration).PrepRequestParamsAndAuth(req)
497495

498-
// Restore our specific headers
499-
req.Header.Set("Accept", preservedAccept)
500-
req.Header.Set("Content-Type", preservedContentType)
501-
502-
c.Sugar.Debugw("Request headers after auth and restoration",
503-
zap.Any("headers", req.Header))
496+
// Restore and log final headers
497+
req.Header.Set("accept", accept)
498+
req.Header.Set("content-type", contentType)
504499

505-
c.Sugar.Infow("Sending custom multipart request",
506-
zap.String("method", method),
507-
zap.String("url", url),
508-
zap.String("filename", fileName),
509-
zap.String("contentType", req.Header.Get("Content-Type")),
510-
zap.String("accept", req.Header.Get("Accept")))
500+
c.Sugar.Infow("Final request headers",
501+
zap.Any("headers", req.Header),
502+
zap.String("accept", req.Header.Get("accept")),
503+
zap.String("content-type", req.Header.Get("content-type")))
511504

512-
startTime := time.Now()
505+
// Send the request
513506
resp, err := c.http.Do(req)
514-
duration := time.Since(startTime)
515-
516507
if err != nil {
517-
c.Sugar.Errorw("Failed to send request",
518-
zap.String("method", method),
519-
zap.String("endpoint", endpoint),
520-
zap.Error(err),
521-
zap.Duration("requestDuration", duration))
522508
return nil, fmt.Errorf("failed to send request: %v", err)
523509
}
524510

525-
c.Sugar.Debugw("Request sent successfully",
526-
zap.String("method", method),
527-
zap.String("endpoint", endpoint),
528-
zap.Int("status_code", resp.StatusCode),
529-
zap.Duration("duration", duration))
530-
531-
c.Sugar.Debugw("Response headers",
532-
zap.Any("headers", resp.Header))
511+
c.Sugar.Debugw("Response received",
512+
zap.Int("statusCode", resp.StatusCode),
513+
zap.Any("responseHeaders", resp.Header))
533514

515+
// Handle response
534516
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
535-
c.Sugar.Info("Request succeeded, processing response")
536517
return resp, response.HandleAPISuccessResponse(resp, out, c.Sugar)
537518
}
538519

520+
// For error responses, try to log the response body
521+
if resp.Body != nil {
522+
bodyBytes, err := io.ReadAll(resp.Body)
523+
if err != nil {
524+
c.Sugar.Warnw("Failed to read error response body", zap.Error(err))
525+
} else {
526+
c.Sugar.Errorw("Request failed",
527+
zap.Int("statusCode", resp.StatusCode),
528+
zap.String("responseBody", string(bodyBytes)))
529+
// Create new reader with same data for error handler
530+
resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
531+
}
532+
}
533+
539534
return resp, response.HandleAPIErrorResponse(resp, c.Sugar)
540535
}

0 commit comments

Comments
 (0)