10

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.

5
  • 1
    first step, use curl to check is server OK. Commented Nov 18, 2015 at 5:59
  • You need to set the method and enctype attributes on your form tag. The input tag is irrelevant for GET/POST decision, multipart encoding and content type setting. Commented Nov 18, 2015 at 6:01
  • 1
    @Volker I thought that specifying method: post and header: {'Content-Type': 'multipart/form-data'} in fetch was enough and that I didn't need a <form> tag, but I tried using a <form> tag as well which looks like this <form id="form" method="post" enctype="multipart/form-data" action="/submit">, I then use new FormData to submit the data in that form but that doesn't work so I guess it must be something else. Commented Nov 18, 2015 at 14:37
  • @JiangYD When I use curl the console where I'm running go run main.go logs nothing as if the submit function was never run. I'm using this command: curl -v -F 'uploadFile=@\"C:/Users/raul-/Desktop/test.png\"' http://localhost:8080/submit in Windows. I get a HTTP/1.1 301 Moved Permanently response and says HTTP error before end of send, stop sending which may explain why the submit function is not run. Commented Nov 18, 2015 at 14:54
  • @JiangYD I updated my question with the entire curl output in case you need more information Commented Nov 18, 2015 at 15:00

2 Answers 2

16

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!

Sign up to request clarification or add additional context in comments.

1 Comment

"note the missing slash" saved my day. Please emphasise your note as this might often be the culprit- +1
-1

Removing the header altogether actually works. Especially when sending the request via fetch or axios.

axios.post(
                    endpoint + "/api/v1/personalslip",
                    {
                      newSlip
                    },
                    {


                     
                    }
                  )
                  .then(res => {
                    console.log(res);
                  });

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.