Why is my go handler not producing valid FormData?

I'm tinkering around trying to get a server written in go to respond to a "mime/multipart" form with a "mime/multipart" form.

With the code below, I'm getting Uncaught (in promise) TypeError: Could not parse content as FormData.

My go looks like this:

package main

import (
    "log"
    "mime/multipart"
    "net/http"
)

func handler(resp http.ResponseWriter, req *http.Request) {
    req.ParseMultipartForm(2097152)
    mw := multipart.NewWriter(resp)
    mw.WriteField("name", req.FormValue("user_name"))
    mw.WriteField("email", req.FormValue("user_email"))
    mw.WriteField("message", req.FormValue("user_message"))
    resp.Header().Set("Content-Type", mw.FormDataContentType())
    mw.Close()
}

func main() {
    http.HandleFunc("/", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

My browser code looks like this:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Your first HTML form</title>
        <link rel="stylesheet" href="form.css">
    </head>

    <body>

<form name="theForm" method="post">
 <ul>
  <li>
    <label for="name">Name:</label>
    <input type="text" id="name" name="user_name">
  </li>
  <li>
    <label for="mail">E-mail:</label>
    <input type="email" id="mail" name="user_email">
  </li>
  <li>
    <label for="msg">Message:</label>
    <textarea id="msg" name="user_message"></textarea>
  </li>
  <li class="button">
    <button type="submit">Send your message</button>
  </li>
 </ul>
</form>
      
      <pre id="response"></pre>
      
<script>
const form = document.forms["theForm"];

form.addEventListener( 'submit', function ( event ) {
  event.preventDefault();
  const fd = new FormData(form);
  fetch('/form-handler', {
    method: 'POST',
    body: fd
  })
  .then(response => response.formData())
  .then(data => document.getElementById("response").innerText = JSON.stringify(data));
});
  
  
</script>

    </body>
</html>

If I change the JavaScript to .then(response => response.text), I get what looks like the right response, so not sure if there's a missing CRLF at the end or what?


Solution 1:

You have to move resp.Header().Set("Content-Type", mw.FormDataContentType()) to above the first write. Like so:

func handler(resp http.ResponseWriter, req *http.Request) {
    req.ParseMultipartForm(2097152)
    mw := multipart.NewWriter(resp)
    resp.Header().Set("Content-Type", mw.FormDataContentType())
    mw.WriteField("name", req.FormValue("user_name"))
    mw.WriteField("email", req.FormValue("user_email"))
    mw.WriteField("message", req.FormValue("user_message"))
    mw.Close()
}

HTTP headers can't be modified after the first write to the ResponseWriter. If no Content-Type is set at that point it defaults to Content-Type: text/plain; charset=utf-8 which the browser can't parse since it needs the boundary which is given in the Content-Type header