web-dev-qa-db-fra.com

POST) en utilisant le type de contenu multipart / form-data

J'essaie de télécharger des images de mon ordinateur sur un site Web à l'aide de go. D'habitude, j'utilise un script bash qui envoie un fichier et une clé au serveur:

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

cela fonctionne bien, mais j'essaie de convertir cette demande en mon programme golang.

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

J'ai essayé ce lien et bien d'autres, mais pour chaque code que j'ai essayé, la réponse du serveur est "aucune image envoyée", et je ne sais pas pourquoi. Si quelqu'un sait ce qui se passe avec l'exemple ci-dessus.

58
Epitouille

Voici un exemple de code.

En bref, vous devrez utiliser le mime/multipart package pour construire le formulaire.

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
}
110
Attila O.

Après avoir dû décoder la réponse acceptée pour cette question afin de l'utiliser dans mes tests unitaires, j'ai finalement obtenu le code refactoré suivant:

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
}

Maintenant, il devrait être assez facile à utiliser:

    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
Luke Hamilton

En ce qui concerne la publication d'attila-o, l'en-tête de la requête n'a pas de limite puisque l'enregistreur est déjà fermé.

// after the close, the bounday will be nil.
w.Close()
...
req.Header.Set("Content-Type", w.FormDataContentType())

Donc, il devrait fermer après le set, je pense.

req.Header.Set("Content-Type", w.FormDataContentType())
w.Close()
0
mttr

Cela pourrait être utile https://github.com/030/go-curl

./go-curl -url \
    http://localhost:9999/service/rest/v1/components?repository=maven-releases \
    -user admin -pass admin123 -F \
    "maven2.asset1=@utils/test-files-multipart/file1.pom,\
    maven2.asset1.extension=pom,\
    maven2.asset2=@utils/test-files-multipart/file1.jar,\
    maven2.asset2.extension=jar,\
    maven2.asset3=@utils/test-files-multipart/file1-sources.jar,\
    maven2.asset3.extension=sources.jar"
0
030

J'ai trouvé ce tutoriel très utile pour clarifier mes confusions sur le téléchargement de fichiers dans Go.

Fondamentalement, vous téléchargez le fichier via ajax en utilisant form-data sur un client et utilisez le petit extrait de code Go suivant sur le serveur:

file, handler, err := r.FormFile("img") // img is the key of the form-data
if err != nil {
    fmt.Println(err)
    return
}
defer file.Close()

fmt.Println("File is good")
fmt.Println(handler.Filename)
fmt.Println()
fmt.Println(handler.Header)


f, err := os.OpenFile(handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
    fmt.Println(err)
    return
}
defer f.Close()
io.Copy(f, file)

Ici r est *http.Request. P.S. cela stocke simplement le fichier dans le même dossier et n'effectue aucune vérification de sécurité.

0
Salvador Dali