Search code examples
formsrestgohttpgo-http

unable to recreate CURL equivalent request in go-lang


I'm using a request bin (running on localhost:8080/anything) to capture what my application sends (kennethreitz/httpbin)

so now i have two similar POST requests using two different user agents, which submits a image file and two other fields.

1.CURL based

curl -X POST 'http://localhost:8080/anything' \
-H 'Authorization: Bearer xyz' \
-F 'file=@"wikipedia_logo.png"' \
-F 'type="image/png"' \
-F 'messaging_product="whatsapp"'

which results in following data on requestbin

{
  "args": {}, 
  "data": "", 
  "files": {
            // take note of this file details
    "file": "data:image/png;base64,<base64-encoded-file>"
  }, 
  "form": {
    "messaging_product": "whatsapp", 
    "type": "image/png"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Authorization": "Bearer xyz", 
    "Content-Length": "27693", 
    "Content-Type": "multipart/form-data; boundary=------------------------94b30407c7fea5c0", 
    "Host": "localhost:8080", 
    "User-Agent": "curl/7.81.0"
  }, 
  "json": null, 
  "method": "POST", 
  "origin": "172.17.0.1", 
  "url": "http://localhost:8080/anything"
}

2.go-http

func main() {
    url := "http://localhost:8080/anything"
    filePath := "wikipedia_logo.png"
    accessToken := "xyz"

    file, err := os.Open(filePath)
    if err != nil {
        panic(err)
    }
    defer file.Close()
    fileBytes, err := io.ReadAll(file)
    if err != nil {
        panic(err)
    }
    var requestBody bytes.Buffer
    writer := multipart.NewWriter(&requestBody)

    part, err := writer.CreateFormFile("file", filePath)
    if err != nil {
        panic(err)
    }
    _, err = part.Write(fileBytes)
    if err != nil {
        panic(err)
    }

    err = writer.WriteField("type", "image/png")
    if err != nil {
        panic(err)
    }
    err = writer.WriteField("messaging_product", "whatsapp")
    if err != nil {
        panic(err)
    }

    err = writer.Close()
    if err != nil {
        panic(err)
    }

    req, err := http.NewRequest("POST", url, &requestBody)
    if err != nil {
        panic(err)
    }
    req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
    req.Header.Set("Content-Type", writer.FormDataContentType())

    // Make the request
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    b, err := io.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }

    fmt.Println(string(b))
}

which results in

{
  "args": {}, 
  "data": "", 
  "files": {
           // why the file type is data:application/octet-stream
    "file": "data:application/octet-stream;base64,<bas64-encode-image>"
  }, 
  "form": {
    "messaging_product": "whatsapp", 
    "type": "image/png"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Authorization": "Bearer xyz", 
    "Content-Length": "27788", 
    "Content-Type": "multipart/form-data; boundary=caaba02d7dcf3f25c63b0c98466a80518331e4e055d2cd43a72780760ee7", 
    "Host": "localhost:8080", 
    "User-Agent": "Go-http-client/1.1"
  }, 
  "json": null, 
  "method": "POST", 
  "origin": "172.17.0.1", 
  "url": "http://localhost:8080/anything"
}

Why the file media type is not image/png in the go-http user agent, how to set it?


Solution

  • It is how CreateFormFile works:

    h.Set("Content-Type", "application/octet-stream")
    

    You can create a general version:

    
    // CreateFormFileExt is a convenience wrapper around CreatePart. It creates
    // a new form-data header with the provided field name and file name and MIME type.
    func CreateFormFileExt(w *multipart.Writer, fieldname, filename, mimeType string) (io.Writer, error) {
        h := make(textproto.MIMEHeader)
        h.Set("Content-Disposition",
            fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
                escapeQuotes(fieldname), escapeQuotes(filename)))
        h.Set("Content-Type", mimeType)
    
        return w.CreatePart(h)
    }
    
    var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
    
    func escapeQuotes(s string) string {
        return quoteEscaper.Replace(s)
    }