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

后端 未结 7 1391
小鲜肉
小鲜肉 2020-11-27 12:16

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         


        
相关标签:
7条回答
  • 2020-11-27 13:04

    After having to decode the accepted answer for this question for use in my unit testing I finally ended up with the follow refactored code:

    func createMultipartFormData(t *testing.T, fieldName, fileName string) (bytes.Buffer, *multipart.Writer) {
        var b bytes.Buffer
        var err error
        w := multipart.NewWriter(&b)
        var fw io.Writer
        file := mustOpen(fileName)
        if fw, err = w.CreateFormFile(fieldName, file.Name()); err != nil {
            t.Errorf("Error creating writer: %v", err)
        }
        if _, err = io.Copy(fw, file); err != nil {
            t.Errorf("Error with io.Copy: %v", err)
        }
        w.Close()
        return b, w
    }
    
    func mustOpen(f string) *os.File {
        r, err := os.Open(f)
        if err != nil {
            pwd, _ := os.Getwd()
            fmt.Println("PWD: ", pwd)
            panic(err)
        }
        return r
    }
    
    

    Now it should be pretty easy to use:

        b, w := createMultipartFormData(t, "image","../luke.png")
    
        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())
    
    0 讨论(0)
  • 2020-11-27 13:04

    To extend on @attila-o answer, here is the code I went with to perform a POST HTTP req in Go with:

    • 1 file
    • configurable file name (f.Name() didn't work)
    • extra form fields.

    Curl representation:

    curl -X POST \
      http://localhost:9091/storage/add \
      -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
      -F owner=0xc916Cfe5c83dD4FC3c3B0Bf2ec2d4e401782875e \
      -F password=$PWD \
      -F file=@./internal/file_example_JPG_500kB.jpg
    

    Go way:

    client := &http.Client{
            Timeout: time.Second * 10,
        }
    req, err := createStoragePostReq(cfg)
    res, err := executeStoragePostReq(client, req)
    
    
    func createStoragePostReq(cfg Config) (*http.Request, error) {
        extraFields := map[string]string{
            "owner": "0xc916cfe5c83dd4fc3c3b0bf2ec2d4e401782875e",
            "password": "pwd",
        }
    
        url := fmt.Sprintf("http://localhost:%d%s", cfg.HttpServerConfig().Port(), lethstorage.AddRoute)
        b, w, err := createMultipartFormData("file","./internal/file_example_JPG_500kB.jpg", "file_example_JPG_500kB.jpg", extraFields)
        if err != nil {
            return nil, err
        }
    
        req, err := http.NewRequest("POST", url, &b)
        if err != nil {
            return nil, err
        }
        req.Header.Set("Content-Type", w.FormDataContentType())
    
        return req, nil
    }
    
    func executeStoragePostReq(client *http.Client, req *http.Request) (lethstorage.AddRes, error) {
        var addRes lethstorage.AddRes
    
        res, err := client.Do(req)
        if err != nil {
            return addRes, err
        }
        defer res.Body.Close()
    
        data, err := ioutil.ReadAll(res.Body)
        if err != nil {
            return addRes, err
        }
    
        err = json.Unmarshal(data, &addRes)
        if err != nil {
            return addRes, err
        }
    
        return addRes, nil
    }
    
    func createMultipartFormData(fileFieldName, filePath string, fileName string, extraFormFields map[string]string) (b bytes.Buffer, w *multipart.Writer, err error) {
        w = multipart.NewWriter(&b)
        var fw io.Writer
        file, err := os.Open(filePath)
    
        if fw, err = w.CreateFormFile(fileFieldName, fileName); err != nil {
            return
        }
        if _, err = io.Copy(fw, file); err != nil {
            return
        }
    
        for k, v := range extraFormFields {
            w.WriteField(k, v)
        }
    
        w.Close()
    
        return
    }
    
    0 讨论(0)
  • 2020-11-27 13:10

    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
    }
    
    0 讨论(0)
  • 2020-11-27 13:12

    Send file from one service to another:

    func UploadFile(network, uri string, f multipart.File, h *multipart.FileHeader) error {
    
        buf := new(bytes.Buffer)
        writer := multipart.NewWriter(buf)
    
        part, err := writer.CreateFormFile("file", h.Filename)
    
        if err != nil {
            log.Println(err)
            return err
        }
    
        b, err := ioutil.ReadAll(f)
    
        if err != nil {
            log.Println(err)
            return err
        }
    
        part.Write(b)
        writer.Close()
    
        req, _ := http.NewRequest("POST", uri, buf)
    
        req.Header.Add("Content-Type", writer.FormDataContentType())
        client := &http.Client{}
        resp, err := client.Do(req)
    
        if err != nil {
            return err
        }
        defer resp.Body.Close()
    
        b, _ = ioutil.ReadAll(resp.Body)
        if resp.StatusCode >= 400 {
            return errors.New(string(b))
        }
        return nil
    }
    
    0 讨论(0)
  • 2020-11-27 13:18

    Had the same issue in a unit test, and if you just need to send in a data to verify that the post this (below) was a little simpler for me. Hope it helps save someone else some time.

    fileReader := strings.NewReader("log file contents go here")
    b := bytes.Buffer{} // buffer to write the request payload into
    fw := multipart.NewWriter(&b)
    fFile, _ := fw.CreateFormFile("file", "./logfile.log")
    io.Copy(fFile, fileReader)
    fw.Close()
    
    req, _ := http.NewRequest(http.MethodPost, url, &b)
    req.Header.Set("Content-Type", fw.FormDataContentType())
    resp, err := http.DefaultClient.Do(req)
    assert.NoError(t, err)
    assert.Equal(t, http.StatusOK, resp.StatusCode)
    

    For me, fileReader is just a strings reader, because I'm posting a log file. In the case of a image, you would send the appropriate reader.

    0 讨论(0)
  • 2020-11-27 13:20

    Here's a function I've used that uses io.Pipe() to avoid reading in the entire file to memory or needing to manage any buffers. It handles only a single file, but could easily be extended to handle more by adding more parts within the goroutine. The happy path works well. The error paths have not hand much testing.

    import (
        "fmt"
        "io"
        "mime/multipart"
        "net/http"
        "os"
    )
    
    func UploadMultipartFile(client *http.Client, uri, key, path string) (*http.Response, error) {
        body, writer := io.Pipe()
    
        req, err := http.NewRequest(http.MethodPost, uri, body)
        if err != nil {
            return nil, err
        }
    
        mwriter := multipart.NewWriter(writer)
        req.Header.Add("Content-Type", mwriter.FormDataContentType())
    
        errchan := make(chan error)
    
        go func() {
            defer close(errchan)
            defer writer.Close()
            defer mwriter.Close()
    
            w, err := mwriter.CreateFormFile(key, path)
            if err != nil {
                errchan <- err
                return
            }
    
            in, err := os.Open(path)
            if err != nil {
                errchan <- err
                return
            }
            defer in.Close()
    
            if written, err := io.Copy(w, in); err != nil {
                errchan <- fmt.Errorf("error copying %s (%d bytes written): %v", path, written, err)
                return
            }
    
            if err := mwriter.Close(); err != nil {
                errchan <- err
                return
            }
        }()
    
        resp, err := client.Do(req)
        merr := <-errchan
    
        if err != nil || merr != nil {
            return resp, fmt.Errorf("http error: %v, multipart error: %v", err, merr)
        }
    
        return resp, nil
    }
    
    0 讨论(0)
提交回复
热议问题