Handle file uploading with go
I managed to solve my problem, so here it is in case someone else needs it. And thanks @JiangYD for the tip of using curl to test the server.
TL;DR
- I wrote
http.HandleFunc("/submit/", submit)
but I was making a POST request to/submit
(note the missing slash) << This is important because of redirections - Don't specify the Content-Type yourself, the browser will do it for you
LONG ANSWER
I did as @JiangYD said and used curl to test the server, I updated my answer with the response. I found odd that there was a 301 Redirect since I didn't put it there, I decided to use the following curl command
curl -v -F 'uploadFile=@\"C:/Users/raul-/Desktop/test.png\"' -L http://localhost:8080/submit
(note the -L) That way curl followed the redirect, though it failed again because, when redirecting, curl switched from POST to GET but with that response I found out that the request to /submit
was being redirected to /submit/
and I remembered that's how I wrote it in the main
function.
After fixing that it still failed, the response was http: no such file
and by looking at the net/http
code I found that it meant the field didn't exist, so I did a quick test iterating over all the field names obtained:
for k, _ := range r.MultipartForm.File {
log.Println(k)
}
I was getting 'uploadFile
as the field name, I removed the single quotes in the curl command and now it uploaded the file perfectly
But it doesn't end here, I now knew the server was working correctly because I could upload a file using curl
but when I tried uploading it through the hosted web page I got an error: no multipart boundary param in Content-Type
.
So I found out I was suppose to include the boundary in the header, I changed fetch to something like this:
fetch('/submit', {
method: 'post',
headers: {
"Content-Type": "multipart/form-data; boundary=------------------------" + boundary
}, body: formData})
I calculate the boundary like this:
var boundary = Math.random().toString().substr(2);
But I still got an error: multipart: NextPart: EOF
So how do you calculate the boundary? I read the spec https://html.spec.whatwg.org/multipage/forms.html#multipart/form-data-encoding-algorithm and found out the boundary is calculated by the algorithm that encodes the file, which in my case is FormData, the FormData API doesn't expose a way to get that boundary but I found out that the browser adds the Content-Type with multipart/form-data
and the boundary automatically if you don't specify it so I removed the headers object from the fetch
call and now it finally works!
redsalt
Updated on July 24, 2022Comments
-
redsalt almost 2 years
I've started playing with go very recently so I'm still a noob, sorry if I make too many mistakes. I've been trying to fix this for a long time but I just don't understand what's going on. In my main.go file I have a main function:
func main() { http.HandleFunc("/", handler) http.HandleFunc("/submit/", submit) log.Fatal(http.ListenAndServe(":8080", nil)) }
The handler function looks like this:
func handler(w http.ResponseWriter, r *http.Request) { data, _ := ioutil.ReadFile("web/index.html") w.Write(data) }
I know this is not the best way to serve a website The submit function looks like this:
func submit(w http.ResponseWriter, r *http.Request) { log.Println("METHOD IS " + r.Method + " AND CONTENT-TYPE IS " + r.Header.Get("Content-Type")) r.ParseMultipartForm(32 << 20) file, header, err := r.FormFile("uploadFile") if err != nil { json.NewEncoder(w).Encode(Response{err.Error(), true}) return } defer file.Close() out, err := os.Create("/tmp/file_" + time.Now().String() + ".png") if err != nil { json.NewEncoder(w).Encode(Response{err.Error(), true}) return } defer out.Close() _, err = io.Copy(out, file) if err != nil { json.NewEncoder(w).Encode(Response{err.Error(), true}) return } json.NewEncoder(w).Encode(Response{"File '" + header.Filename + "' submited successfully", false}) }
The problem is when the submit function is executed,
r.Method
isGET
andr.Header.Get("Content-Type")
is an empty string, then it continues until the first if where r.FormFile returns the following error:request Content-Type isn't multipart/form-data
I don't understand why r.Method is always GET and there's no Content-Type. I've tried to do the index.html in many different ways but r.Method is always GET and Content-Type is empty. Here's the function in index.html that uploads a file:function upload() { var formData = new FormData(); formData.append('uploadFile', document.querySelector('#file-input').files[0]); fetch('/submit', { method: 'post', headers: { "Content-Type": "multipart/form-data" }, body: formData }).then(function json(response) { return response.json() }).then(function(data) { window.console.log('Request succeeded with JSON response', data); }).catch(function(error) { window.console.log('Request failed', error); }); }
And here's the HTML:
<input id="file-input" type="file" name="uploadFile" />
Note that the tag is not inside a tag, I thought that could be the problem so I changed both the function and the HTML to something like this:
function upload() { fetch('/submit', { method: 'post', headers: { "Content-Type": "multipart/form-data" }, body: new FormData(document.querySelector('#form') }).then(function json(response) { return response.json() }).then(function(data) { window.console.log('Request succeeded with JSON response', data); }).catch(function(error) { window.console.log('Request failed', error); }); } <form id="form" method="post" enctype="multipart/form-data" action="/submit"><input id="file-input" type="file" name="uploadFile" /></form>
But that didn't work neither. I've searched with Google how to use fetch() and how to receive a file upload from go and I've seen that they are pretty similar to mine, I don't know what I'm doing wrong.
UPDATE: After using
curl -v -F 'uploadFile=@\"C:/Users/raul-/Desktop/test.png\"' http://localhost:8080/submit
I get the following output:* Trying ::1... * Connected to localhost (::1) port 8080 (#0) > POST /submit HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.45.0 > Accept: */* > Content-Length: 522 > Expect: 100-continue > Content-Type: multipart/form-data; boundary=---------------------------a17d4e54fcec53f8 > < HTTP/1.1 301 Moved Permanently < Location: /submit/ < Date: Wed, 18 Nov 2015 14:48:38 GMT < Content-Length: 0 < Content-Type: text/plain; charset=utf-8 * HTTP error before end of send, stop sending < * Closing connection 0
The console where I'm running
go run main.go
outputs nothing when using curl. -
darkdefender27 over 6 yearsThanks for the insight. Helped me a lot.
-
Odd about 3 years"note the missing slash" saved my day. Please emphasise your note as this might often be the culprit- +1