1. Parse Query String and x-www-form-urlencoded Format Body#

if e := r.ParseForm(); err != nil {
  err = fmt.Errorf("failed to parse username and password: %v", e)
  return
}
username = r.Form.Get("username")
password = r.Form.Get("password")

According to Golang docs, r.ParseForm() will try to parse both query string and request body in Go, but there is a condition: the Content-Type header of the reuqest must be application/x-www-form-urlencoded. Otherwise, the request body will be ignored by r.ParseForm(), it just tries to parse query string resides in the URL.

If you don’t know query string or application/x-www-form-urlencoded: https://blog.yorforger.cc/p/http-headers-1/

First, there is a simple handler which parses form data or query string:

func handlePostForm(w http.ResponseWriter, r *http.Request) {
	// r.ParseForm() parses username and password in form or query string.
  // And puts the parsed data into r.Form which is a map value.
	if err := r.ParseForm(); err != nil {
		http.Error(w, fmt.Sprintf("failed to parse username and password: %v", err),
			http.StatusMethodNotAllowed)
		return
	}
	username := r.Form.Get("username")
	password := r.Form.Get("password")
	if username == "" || password == "" {
		http.Error(w, "failed to parse username and password: no username or password",
			http.StatusMethodNotAllowed)
		return
	}
	_, _ = fmt.Fprint(w, fmt.Sprintf("username:%v password:%v\n", username, password))
}

func main() {
	mux := http.NewServeMux()
  // Add pattern and its handdler into mux
	mux.HandleFunc("/postform", handlePostForm)
  // http.ListenAndServe will call mux.ServerHTTP(w, r)
	_ = http.ListenAndServe(":8080", mux)
}

And the request below will get same result:

$ curl -X POST "localhost:8080/postform?username=david&password=778899a"
$ curl -X POST localhost:8080/postform -d "username=davidzhu&password=778899a"

But the code below won’t work, because the Content-Type header is not application/x-www-form-urlencoded:

$ curl -X POST localhost:8080/postform 
	-d "username=davidzhu&password=778899a" 
	-H "Content-Type: application/json"
	
failed to parse username and password: no username or password

2. RawQuery vs Query()#

If there are whitespaces in query string, the whitespace will be encoded to %20 or + automatically, js code:

async function getUrl(filepath) {
    const url = "?asset=" + "back ground.png"
    let response = await fetch(url)
    ...
}

The request URL will be: http://localhost:8080?filepath=back%20ground.png. Therefore, if you use r.URL.RawQuery in Go:

log.Println(r.URL.RawQuery)
log.Println(r.URL.Query())

This will print:

2023/10/29 12:19:36 filepath=back%20ground.png
2023/10/29 12:19:36 map[filepath:[back ground.png]]

As you can see, r.URL.RawQuery isn’t friendly to string with whitespace, you should try to use r.URL.Query() to emliminate the %20 characters.

So the recommended way is as below:

func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// handle asset.
	const assetPrefix = "asset="
	if strings.HasPrefix(r.URL.RawQuery, assetPrefix) {
    // r.URL.Query() returns a map[string][]string
		assetName := r.URL.Query()[strings.TrimSuffix(assetPrefix, "=")][0]
		s.asset(w, r, assetName)
		return
	}
  ...
}

3. Parse Json Data from Request Body#

func handlePostBody(w http.ResponseWriter, r *http.Request) {
	// Parse username and password in form.
	type info struct {
		Username string `json:"username"`
		Password string `json:"password"`
	}
	user := info{}
  // note that r.Body is an io.Reader
  // which means decoder.Decode() method will consume data stroed in r.Body
  // you cannot read same data twice
	decoder := json.NewDecoder(r.Body)
	err := decoder.Decode(&user)
	if err != nil {
		http.Error(w, fmt.Sprintf("failed to decode post body:%v", err),
			http.StatusMethodNotAllowed)
	}
	_, _ = fmt.Fprint(w, fmt.Sprintf("username:%v password:%v\n", user.Username, user.Password))
}

The command below won’t work:

$ curl -X POST localhost:8080/postbody 
	-d "username=davidzhu&password=778899a" 
	-H "Content-Type: application/json"
failed to decode post body:invalid character 'u' looking for beginning of value
username: password:

Because the data format isn’t correct, this will work:

$ curl localhost:8080/postbody -d '{"username":"davidzhu", "password":"778899a"}'