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
is GET
and r.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.
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
http.HandleFunc("/submit/", submit)
but I was making a POST request to/submit
(note the missing slash) << This is important because of redirectionsLONG 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
(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 themain
function.After fixing that it still failed, the response was
http: no such file
and by looking at thenet/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:I was getting
'uploadFile
as the field name, I removed the single quotes in the curl command and now it uploaded the file perfectlyBut 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:
I calculate the boundary like this:
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 withmultipart/form-data
and the boundary automatically if you don't specify it so I removed the headers object from thefetch
call and now it finally works!