53

I'm playing around with Mux and net/http. Lately, I'm trying to get a simple server with one endpoint to accept a file upload.

Here's the code I've got so far:

server.go

package main

import (
    "fmt"
    "github.com/gorilla/mux"
    "log"
    "net/http"
)

func main() {
    router := mux.NewRouter()
    router.
        Path("/upload").
        Methods("POST").
        HandlerFunc(UploadCsv)
    fmt.Println("Starting")
    log.Fatal(http.ListenAndServe(":8080", router))
}

endpoint.go

package main

import (
    "fmt"
    "net/http"
)

func UploadFile(w http.ResponseWriter, r *http.Request) {
    err := r.ParseMultipartForm(5 * 1024 * 1024)
    if err != nil {
        panic(err)
    }

    fmt.Println(r.FormValue("fileupload"))
}

I think I've narrowed the issue down to actually retrieving the body from the request inside UploadFile. When I run this cURL command:

curl http://localhost:8080/upload -F "fileupload=@test.txt" -vvv

I get an empty response (as expected; I'm not printing to the ResponseWriter), but I just get a new (empty) line printed at the prompt where I'm running the server, instead of the request body.

I'm sending the file as multipart (AFAIK, implied by using -F rather than -d in cURL), and cURL's verbose output is showing 502 bytes sent:

$ curl http://localhost:8080/upload -F "fileupload=@test.txt" -vvv
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /upload HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
> Content-Length: 520
> Expect: 100-continue
> Content-Type: multipart/form-data; boundary=------------------------b578878d86779dc5
> 
< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
< Date: Fri, 18 Nov 2016 19:01:50 GMT
< Content-Length: 0
< Content-Type: text/plain; charset=utf-8
< 
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact

What's the proper way to receive files uploaded as multipart form data using a net/http server in Go?

user7179784
  • 531
  • 1
  • 4
  • 3
  • 5
    maybe you are looking for https://golang.org/pkg/net/http/#Request.FormFile ? –  Nov 18 '16 at 19:28
  • Also this is what I have used to parse multiple files: https://golang.org/pkg/mime/multipart/#Form – squiguy Nov 18 '16 at 19:34
  • 1
    @mh-cbon that's it! I get a `[]byte`, which prints properly, which means I've got something to work with. Thanks! – user7179784 Nov 18 '16 at 19:43
  • I have an example here https://github.com/yanpozka/go-httprouter-upfiles-token/blob/master/handlers.go#L49 – Yandry Pozo Nov 19 '16 at 00:11
  • 2
    @mh-cbon please add your answer as an answer so it can be marked as accepted and this question can be filtered out when people are looking for questions to answer. – ijt Mar 05 '17 at 06:13
  • can this be done with a file and a struct in the same request? – filthy_wizard Jan 17 '18 at 08:32

3 Answers3

51

Here's a quick example

func ReceiveFile(w http.ResponseWriter, r *http.Request) {
    r.ParseMultipartForm(32 << 20) // limit your max input length!
    var buf bytes.Buffer
    // in your case file would be fileupload
    file, header, err := r.FormFile("file")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    name := strings.Split(header.Filename, ".")
    fmt.Printf("File name %s\n", name[0])
    // Copy the file data to my buffer
    io.Copy(&buf, file)
    // do something with the contents...
    // I normally have a struct defined and unmarshal into a struct, but this will
    // work as an example
    contents := buf.String()
    fmt.Println(contents)
    // I reset the buffer in case I want to use it again
    // reduces memory allocations in more intense projects
    buf.Reset()
    // do something else
    // etc write header
    return
}
reticentroot
  • 3,612
  • 2
  • 22
  • 39
  • 11
    Note that to avoid memory consumption you might write ondisk directly at `io.Copy(&Buf, file)` where Buf is to replace with a File handler. –  Mar 12 '17 at 17:41
  • True that, large file no es bueno. – reticentroot Mar 12 '17 at 17:44
  • i also put +1 on below answer from keith wachira for pointing it out. –  Dec 18 '19 at 20:23
  • I checked the error that's return on `defer file.Close()` and it seems the file is unable to be closed -- is that actually needed here? – Josh Hibschman Nov 09 '20 at 22:21
  • Sorry I'm unfamiliar with bitwise operators, but does `32 << 20` have a special meaning? Is there a reason you use it in this example? – ban_javascript Jan 30 '22 at 11:00
  • 1
    @ban_javascript in this use case it's shorthand to expand and convert 32 into 32MB as the method takes a read limit in bytes. see this article demonstrating it further. https://medium.com/@owlwalks/dont-parse-everything-from-client-multipart-post-golang-9280d23cd4ad – reticentroot Apr 05 '23 at 21:52
21

You should use FormFile instead of FormValue:

file, fileHeader, err := r.FormFile("fileupload")
defer file.Close()

// copy example
f, err := os.OpenFile("./downloaded", os.O_WRONLY|os.O_CREATE, 0666)
defer f.Close()
io.Copy(f, file)
Scott Stensland
  • 26,870
  • 12
  • 93
  • 104
Alex Pliutau
  • 21,392
  • 27
  • 113
  • 143
11

Here a function i wrote to help me in uploading my files.You can check the full version here . How to upload files in golang

package helpers

import (
    "io"
    "net/http"
    "os"
)

// This function returns the filename(to save in database) of the saved file
// or an error if it occurs
func FileUpload(r *http.Request) (string, error) {
    // ParseMultipartForm parses a request body as multipart/form-data
    r.ParseMultipartForm(32 << 20)

    file, handler, err := r.FormFile("file") // Retrieve the file from form data

    if err != nil {
        return "", err
    }
    defer file.Close()                       // Close the file when we finish

    // This is path which we want to store the file
    f, err := os.OpenFile("/pathToStoreFile/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)

    if err != nil {
        return "", err
    }

    // Copy the file to the destination path
    io.Copy(f, file)

    return handler.Filename, nil
}
itachi sasuke
  • 259
  • 5
  • 6