Ich versuche, Bilder von meinem Computer mit go auf eine Website hochzuladen. Normalerweise verwende ich ein Bash-Skript, das eine Datei und einen Schlüssel an den Server sendet:
curl -F "image"=@"IMAGEFILE" -F "key"="KEY" URL
es funktioniert gut, aber ich versuche, diese Anfrage in mein Golang-Programm zu konvertieren.
http://matt.aimonetti.net/posts/2013/07/01/golang-multipart-file-upload-example/
Ich habe diesen und viele andere Links ausprobiert, aber für jeden Code, den ich versuche, lautet die Antwort vom Server "kein Bild gesendet", und ich habe keine Ahnung, warum. Wenn jemand weiß, was mit dem obigen Beispiel passiert.
file-upload
curl
go
multipart
Epitouille
quelle
quelle
Antworten:
Hier ist ein Beispielcode.
Kurz gesagt, Sie müssen das
mime/multipart
Paket verwenden , um das Formular zu erstellen.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 }
quelle
form-data; name="data"; filename="%s"
, fn) partHeader.Add ("Inhaltsdisposition", disp) partHeader.Add ("Inhaltstyp", "image / jpeg") part, err: = writer.CreatePart (partHeader) </ pre>file.Name()
bei mir nicht funktioniert. Scheint, als würde der an os.Open () übergebene Dateipfad kein Name zurückgegeben ...(string) (len=37) "./internal/file_example_JPG_500kB.jpg"
// Name returns the name of the file as presented to Open. func (f *File) Name() string { return f.name }
Wenn ich den Dateinamenw.CreateFormFile()
darin fest codiere, funktioniert er einwandfrei . Danke AttilaHier ist eine Funktion, die ich verwendet habe, um
io.Pipe()
zu vermeiden, dass die gesamte Datei in den Speicher eingelesen wird oder Puffer verwaltet werden müssen. Es verarbeitet nur eine einzelne Datei, kann jedoch leicht erweitert werden, um mehr zu verarbeiten, indem mehr Teile innerhalb der Goroutine hinzugefügt werden. Der glückliche Weg funktioniert gut. Die Fehlerpfade haben nicht viel getestet.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 }
quelle
Nachdem ich die akzeptierte Antwort auf diese Frage zur Verwendung in meinen Unit-Tests dekodieren musste, erhielt ich schließlich den folgenden überarbeiteten 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 }
Jetzt sollte es ziemlich einfach zu bedienen sein:
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())
quelle
Um die @ attila-o-Antwort zu erweitern, ist hier der Code, mit dem ich eine POST-HTTP-Anforderung in Go with ausgeführt habe:
Curl-Darstellung:
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
Geh weg:
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 }
quelle
Hatte das gleiche Problem bei einem Unit-Test, und wenn Sie nur Daten senden müssen, um zu überprüfen, ob der Beitrag (unten) für mich etwas einfacher war. Hoffe, es hilft jemand anderem Zeit zu sparen.
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)
Für mich ist fileReader nur ein String-Reader, da ich eine Protokolldatei veröffentliche. Im Falle eines Bildes würden Sie den entsprechenden Leser senden.
quelle
Datei von einem Dienst an einen anderen senden:
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 }
quelle
Ich fand dieses Tutorial sehr hilfreich, um meine Verwirrungen über das Hochladen von Dateien in Go zu klären.
Grundsätzlich laden Sie die Datei über Ajax
form-data
auf einem Client hoch und verwenden den folgenden kleinen Ausschnitt aus Go-Code auf dem Server: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)
Hier
r
ist*http.Request
. PS: Dadurch wird die Datei nur im selben Ordner gespeichert und es werden keine Sicherheitsüberprüfungen durchgeführt.quelle