Search code examples
file-uploadcurlgomultipart

POST data using the Content-Type multipart/form-data


I'm trying to upload images from my computer to a website using go. Usually, I use a bash script that sends a file and a key to the server:

curl -F "image"=@"IMAGEFILE" -F "key"="KEY" URL

it works fine, but I'm trying to convert this request into my golang program.

http://matt.aimonetti.net/posts/2013/07/01/golang-multipart-file-upload-example/

I tried this link and many others, but, for each code that I try, the response from the server is "no image sent", and I've no idea why. If someone knows what's happening with the example above.


Solution

  • Here's some sample code.

    In short, you'll need to use the mime/multipart package to build the form.

    package main
    
    import (
        "bytes"
        "fmt"
        "io"
        "mime/multipart"
        "net/http"
        "net/http/httptest"
        "net/http/httputil"
        "os"
        "strings"
    )
    
    func main() {
    
        var client *http.Client
        var remoteURL string
        {
            //setup a mocked http client.
            ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                b, err := httputil.DumpRequest(r, true)
                if err != nil {
                    panic(err)
                }
                fmt.Printf("%s", b)
            }))
            defer ts.Close()
            client = ts.Client()
            remoteURL = ts.URL
        }
    
        //prepare the reader instances to encode
        values := map[string]io.Reader{
            "file":  mustOpen("main.go"), // lets assume its this file
            "other": strings.NewReader("hello world!"),
        }
        err := Upload(client, remoteURL, values)
        if err != nil {
            panic(err)
        }
    }
    
    func Upload(client *http.Client, url string, values map[string]io.Reader) (err error) {
        // Prepare a form that you will submit to that URL.
        var b bytes.Buffer
        w := multipart.NewWriter(&b)
        for key, r := range values {
            var fw io.Writer
            if x, ok := r.(io.Closer); ok {
                defer x.Close()
            }
            // Add an image file
            if x, ok := r.(*os.File); ok {
                if fw, err = w.CreateFormFile(key, x.Name()); err != nil {
                    return
                }
            } else {
                // Add other fields
                if fw, err = w.CreateFormField(key); err != nil {
                    return
                }
            }
            if _, err = io.Copy(fw, r); err != nil {
                return err
            }
    
        }
        // Don't forget to close the multipart writer.
        // If you don't close it, your request will be missing the terminating boundary.
        w.Close()
    
        // Now that you have a form, you can submit it to your handler.
        req, err := http.NewRequest("POST", url, &b)
        if err != nil {
            return
        }
        // Don't forget to set the content type, this will contain the boundary.
        req.Header.Set("Content-Type", w.FormDataContentType())
    
        // Submit the request
        res, err := client.Do(req)
        if err != nil {
            return
        }
    
        // Check the response
        if res.StatusCode != http.StatusOK {
            err = fmt.Errorf("bad status: %s", res.Status)
        }
        return
    }
    
    func mustOpen(f string) *os.File {
        r, err := os.Open(f)
        if err != nil {
            panic(err)
        }
        return r
    }