Search code examples
gogorilla

Request body empty after posting data to endpoint


I'm not sure why the data being posted is not present when running the following curl request:

curl --request POST http://localhost:4000 --header "Content-Type: application/json" --data '{ "hostname": "bbc.co.uk" }'

against the code below. It's essentially just posting json with the variable hostname but for some reason it's not appearing in req.Body or appearing in the Domain structure array. Please note this is based on this tutorial

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "fmt"

    "github.com/gorilla/mux"
    "github.com/gorilla/handlers"
)

type Domain struct {
    hostname string   `json:"hostname,omitempty"`
}

var domains []Domain

func CreateDomainEndpoint(w http.ResponseWriter, req *http.Request) {
    var domain Domain

    fmt.Println(req.Body)
    _ = json.NewDecoder(req.Body).Decode(&domain)
    domains = append(domains, domain)
    json.NewEncoder(w).Encode(domains)
}

func main() {
    router := mux.NewRouter()
    router.HandleFunc("/", CreateDomainEndpoint).Methods("POST")

    log.Fatal(http.ListenAndServe(":4000", handlers.CORS(handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"}), handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), handlers.AllowedOrigins([]string{"*"}))(router)))
}

Solution

    • The JSON codec ignores the hostname field because the field is not exported. Fix by capitalizing the name of the field.
    • There is a data race on domains. Fix by protecting access to the variable with a mutex.
    • The application ignores errors. Fix by checking for and handing the error returned from the JSON decoder.

    Here's the code with fixes:

    package main
    
    import (
        "encoding/json"
        "log"
        "net/http"
        "sync"
    
        "github.com/gorilla/handlers"
        "github.com/gorilla/mux"
    )
    
    type Domain struct {
        Hostname string `json:"hostname,omitempty"`
    }
    
    var (
        domains []Domain
        mu      sync.Mutex
    )
    
    func CreateDomainEndpoint(w http.ResponseWriter, req *http.Request) {
        var domain Domain
        if err := json.NewDecoder(req.Body).Decode(&domain); err != nil {
            http.Error(w, "bad request", 400)
            return
        }
        mu.Lock()
        domains = append(domains, domain)
        // To avoid holding the mutex while writing to the
        // response body, make a local copy of the slice header.
        d := domains
        mu.Unlock()
    
        json.NewEncoder(w).Encode(d)
    }
    
    func main() {
        router := mux.NewRouter()
        router.HandleFunc("/", CreateDomainEndpoint).Methods("POST")
    
        log.Fatal(http.ListenAndServe(":4000", handlers.CORS(handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"}), handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), handlers.AllowedOrigins([]string{"*"}))(router)))
    }