curl-to-go icon indicating copy to clipboard operation
curl-to-go copied to clipboard

Handling the -F option of Curl

Open WyoMurf opened this issue 4 years ago • 2 comments

Are you requesting support for a new curl flag? If so, what is the flag and the equivalent Go code?

# (curl –X POST https://ssomesite.com/convert \
-F “media[file_data]=@/tmp/Example.wav;type=audio/x-wav” \
-F “account_did=5555555555” \
-F “api_key=a28f48da8da33402daef356abc567abfedc” \
-F “media[external_id]=123456789” \
-F “media[sender_id]=444-555-6666” \
-F “media[sender_name]=Mark Smith” \
-F “media[receiver_id]=333-555-1212” \
-F “media[receiver_name]=James Jones” \
-F “media[external_id]=4aef5432” \
-F “media[external_data]=Free-form text relative to your use case.”
// (no idea

I am looking at trying to make a complex request (like the above), and with the curl request (see above), code it up using the Go's net/http package.

Now, my main concern is that I am relatively new to the net/http package. It looks and feels pretty powerful, so I suspect this could be done. Your little curl-to-go program, and the example code have fired the imagination. I think I might be able to supply a -F option to code, but I need a nudge in the right direction.

If you were to code up (in go) the conversion, how would you (from a high-level perspective) do it? A little point in the right direction can get me started pretty quickly.

The curl above results in this sequence: (trace from curl): == Info: About to connect() to eps-4voice.nuancemobility.net port 443 (#0) == Info: Trying 8.39.192.14... == Info: connected == Info: Connected to eps-4voice.nuancemobility.net (8.39.192.14) port 443 (#0) == Info: Initializing NSS with certpath: sql:/etc/pki/nssdb == Info: warning: ignoring value of ssl.verifyhost == Info: skipping SSL peer certificate verification == Info: NSS: client certificate not found (nickname not specified) == Info: SSL connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 == Info: Server certificate: == Info: subject: CN=*.nuancemobility.net,OU=BOS,O=Nuance Communications Inc.,L=Burlington,ST=Massachusetts,C=US == Info: start date: Jul 10 00:00:00 2020 GMT == Info: expire date: Jul 11 12:00:00 2022 GMT == Info: common name: *.nuancemobility.net == Info: issuer: CN=DigiCert Global CA G2,O=DigiCert Inc,C=US => Send header, 344 bytes (0x158) 0000: POST /conversions/4voice-us-voicemail HTTP/1.1 0030: User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 0070: NSS/3.44 zlib/1.2.3 libidn/1.18 libssh2/1.4.2 00a0: Host: eps-4voice.nuancemobility.net 00c5: Accept: / 00d2: Content-Length: 202234 00ea: Expect: 100-continue 0100: Content-Type: multipart/form-data; boundary=-------------------- 0140: --------744dab5b632c 0156: <= Recv header, 23 bytes (0x17) 0000: HTTP/1.1 100 Continue => Send data, 187 bytes (0xbb) 0000: ------------------------------744dab5b632c 002c: Content-Disposition: form-data; name="media[file_data]"; filenam 006c: e="Example.wav" 009e: Content-Type: audio/x-wav 00b9: => Send data, 16383 bytes (0x3fff) 0000: RIFF$...WAVEfmt ........@....>......data.............. ......... 0040: ............. .......(......................................... 0080: ................................................................ 00c0: ................................................................ 0100: ......0............. ............. .......0..................... ... (15 16k blocks of info snipped out...) 1080: ....D...............T...................t.............$......... 10c0: ............|$|.|,|&..............D.....D.<.............D...D.d. 1100: ..............<.......$.4............... => Send data, 1043 bytes (0x413) 0000: 0002: ------------------------------744dab5b632c 002e: Content-Disposition: form-data; name="account_did" 0062: 0064: 5555555555 0070: ------------------------------744dab5b632c 009c: Content-Disposition: form-data; name="api_key" 00cc: 00ce: a28f48da8da33402daef356abc567abfedc 00f8: ------------------------------744dab5b632c 0124: Content-Disposition: form-data; name="media[sender_id]" 015d: 015f: 444-555-6666 0164: ------------------------------744dab5b632c 0190: Content-Disposition: form-data; name="media[sender_name]" 01cb: 01cd: Mark Smith 01dc: ------------------------------744dab5b632c 0208: Content-Disposition: form-data; name="media[receiver_id]" 0243: 0245: 333-555-1212 025b: ------------------------------744dab5b632c 0287: Content-Disposition: form-data; name="media[receiver_name]" 02c4: 02c6: James Jones 02d9: ------------------------------744dab5b632c 0305: Content-Disposition: form-data; name="media[external_id]" 0340: 0342: 4aef5432 0362: ------------------------------744dab5b632c 038e: Content-Disposition: form-data; name="media[external_data]" 03cb: 03cd: Free-form text relative to your use case. 03e5: ------------------------------744dab5b632c--

Got any words of wisdom?

WyoMurf avatar Sep 01 '20 00:09 WyoMurf

Here are the docs for curl -F: https://curl.haxx.se/docs/manpage.html#-F

This is not a simple one. :) However, it's ok to partially implement a flag as long as we make it clear what features or forms/syntaxes are supported.

What we need to do is make a POST request with multipart/form-data body in Go; here's a good starting point: https://www.google.com/search?q=golang+http+post+multipart%2Fform-data

You'll probably want to become familiar with how multipart uploads work, if you aren't already, and then there's a great package in the standard library that makes it pretty easy: https://golang.org/pkg/mime/multipart/

Here's a really simple example from the search results that shows the most basic use, I think: https://gist.github.com/andrewmilson/19185aab2347f6ad29f5

Hopefully that is helpful in giving you a good starting point!

mholt avatar Sep 01 '20 00:09 mholt

This was the simplest example I could come up with. I wrapped the multipart code in a function, as you need to Close it before you can make a request:

package main

import (
   "bytes"
   "io"
   "mime/multipart"
   "net/http"
   "os"
   "strings"
)

func createForm(form map[string]string) (string, io.Reader, error) {
   body := new(bytes.Buffer)
   mp := multipart.NewWriter(body)
   defer mp.Close()
   for key, val := range form {
      if strings.HasPrefix(val, "@") {
         val = val[1:]
         file, err := os.Open(val)
         if err != nil { return "", nil, err }
         defer file.Close()
         part, err := mp.CreateFormFile(key, val)
         if err != nil { return "", nil, err }
         io.Copy(part, file)
      } else {
         mp.WriteField(key, val)
      }
   }
   return mp.FormDataContentType(), body, nil
}

func main() {
   form := map[string]string{"file_data": "@example.wav", "account": "5555555555"}
   ct, body, err := createForm(form)
   if err != nil {
      panic(err)
   }
   http.Post("https://github.com", ct, body)
}

89z avatar Jun 07 '21 01:06 89z